]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/arguments.cc
More conversions, startup should be done now
[thirdparty/pdns.git] / pdns / arguments.cc
CommitLineData
12c86877 1/*
12471842
PL
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
12c86877 25#include "arguments.hh"
5b2cb3be 26#include <boost/algorithm/string.hpp>
2dfec518 27#include <boost/algorithm/string/compare.hpp>
4f6ef75f 28#include <boost/algorithm/string/predicate.hpp>
fa8fd4d2 29
61b26744 30#include "namespaces.hh"
4f6ef75f 31#include "logger.hh"
8a16d778
AT
32#include <sys/types.h>
33#include <dirent.h>
34#include <sys/stat.h>
35#include <unistd.h>
64f3490e 36#include <limits.h>
12c86877 37
12c86877
BH
38const ArgvMap::param_t::const_iterator ArgvMap::begin()
39{
9d6e970b 40 return d_params.begin();
12c86877
BH
41}
42
43const ArgvMap::param_t::const_iterator ArgvMap::end()
44{
9d6e970b 45 return d_params.end();
12c86877
BH
46}
47
48string & ArgvMap::set(const string &var)
49{
9d6e970b 50 return d_params[var];
12c86877
BH
51}
52
8864bdf6
KM
53void ArgvMap::setDefault(const string &var, const string &value)
54{
55 if(! defaultmap.count(var))
56 defaultmap.insert(pair<string, string>(var, value));
57}
58
59void ArgvMap::setDefaults()
60{
9d6e970b
CH
61 for (const auto& i: d_params)
62 if(! defaultmap.count(i.first))
63 defaultmap.insert(i);
8864bdf6
KM
64}
65
12c86877 66bool ArgvMap::mustDo(const string &var)
bd11bd1d 67{
12c86877
BH
68 return ((*this)[var]!="no") && ((*this)[var]!="off");
69}
70
71vector<string>ArgvMap::list()
72{
73 vector<string> ret;
9d6e970b
CH
74 for (const auto& i: d_params)
75 ret.push_back(i.first);
12c86877
BH
76 return ret;
77}
78
79string ArgvMap::getHelp(const string &item)
80{
81 return helpmap[item];
82}
83
84string & ArgvMap::set(const string &var, const string &help)
85{
86 helpmap[var]=help;
87 d_typeMap[var]="Parameter";
88 return set(var);
89}
90
91void ArgvMap::setCmd(const string &var, const string &help)
92{
93 helpmap[var]=help;
94 d_typeMap[var]="Command";
95 set(var)="no";
96}
97
98string & ArgvMap::setSwitch(const string &var, const string &help)
99{
100 helpmap[var]=help;
101 d_typeMap[var]="Switch";
102 return set(var);
103}
104
105
12c86877
BH
106bool ArgvMap::contains(const string &var, const string &val)
107{
9d6e970b
CH
108 const auto& param = d_params.find(var);
109 if(param == d_params.end() || param->second.empty()) {
102eb646
BH
110 return false;
111 }
562588a3 112 vector<string> parts;
562588a3 113
9d6e970b 114 stringtok(parts, param->second, ", \t");
93a471a3
CHB
115 for (const auto& part: parts) {
116 if (part == val) {
562588a3
BH
117 return true;
118 }
119 }
120
121 return false;
12c86877
BH
122}
123
12c86877
BH
124string ArgvMap::helpstring(string prefix)
125{
126 if(prefix=="no")
127 prefix="";
128
129 string help;
93a471a3
CHB
130
131 for (const auto& i: helpmap) {
132 if(!prefix.empty() && i.first.find(prefix) != 0) // only print items with prefix
133 continue;
12c86877
BH
134
135 help+=" --";
93a471a3 136 help+=i.first;
12c86877 137
93a471a3 138 string type=d_typeMap[i.first];
12c86877
BH
139
140 if(type=="Parameter")
4957a608 141 help+="=...";
12c86877 142 else if(type=="Switch")
4957a608 143 {
93a471a3
CHB
144 help+=" | --"+i.first+"=yes";
145 help+=" | --"+i.first+"=no";
4957a608 146 }
12c86877
BH
147
148
149 help+="\n\t";
93a471a3 150 help+=i.second;
12c86877
BH
151 help+="\n";
152
153 }
154 return help;
155}
156
9d6e970b
CH
157const string ArgvMap::formatOne(bool running, bool full, const string &var, const string &help, const string& theDefault, const string& current)
158{
159 string out;
160
161 if (!running || full) {
162 out += "#################################\n";
163 out += "# ";
164 out += var;
165 out += "\t";
166 out += help;
167 out += "\n#\n";
168 } else {
169 if (theDefault == current) {
170 return "";
171 }
172 }
173
174 if (! running || theDefault == current) {
175 out += "# ";
176 }
177
178 if (running) {
179 out += var + "=" + current + "\n";
180 if (full) {
181 out += "\n";
182 }
183 } else {
184 out += var + "=" + theDefault + "\n\n";
185 }
186
187 return out;
188}
189
190// If running and full, only changed settings are returned.
8864bdf6 191string ArgvMap::configstring(bool running, bool full)
12c86877
BH
192{
193 string help;
6bef3d91 194
8864bdf6
KM
195 if (running)
196 help="# Autogenerated configuration file based on running instance ("+nowTime()+")\n\n";
6bef3d91 197 else
8864bdf6
KM
198 help="# Autogenerated configuration file template\n\n";
199
9d6e970b
CH
200 // Affects parsing, should come first.
201 help += formatOne(running, full, "ignore-unknown-settings", helpmap["ignore-unknown-settings"], defaultmap["ignore-unknown-settings"], d_params["ignore-unknown-settings"]);
202
93a471a3 203 for(const auto& i: helpmap) {
9d6e970b
CH
204 if (d_typeMap[i.first] == "Command")
205 continue;
206 if (i.first == "ignore-unknown-settings")
c7efa8ff
RA
207 continue;
208
9d6e970b
CH
209 if (!defaultmap.count(i.first)) {
210 throw ArgException(string("Default for setting '")+i.first+"' not set");
8864bdf6
KM
211 }
212
9d6e970b
CH
213 help += formatOne(running, full, i.first, i.second, defaultmap[i.first], d_params[i.first]);
214 }
8864bdf6 215
9d6e970b
CH
216 if (running) {
217 for(const auto& i: d_unknownParams) {
218 help += formatOne(running, full, i.first, "unknown setting", "", i.second);
12c86877 219 }
c7efa8ff 220 }
9d6e970b 221
12c86877
BH
222 return help;
223}
224
12c86877
BH
225const string & ArgvMap::operator[](const string &arg)
226{
227 if(!parmIsset(arg))
228 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
229
9d6e970b 230 return d_params[arg];
12c86877
BH
231}
232
cc2978dc
BH
233mode_t ArgvMap::asMode(const string &arg)
234{
235 mode_t mode;
236 const char *cptr_orig;
4646277d 237 char *cptr_ret = nullptr;
cc2978dc
BH
238
239 if(!parmIsset(arg))
240 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
241
9d6e970b 242 cptr_orig = d_params[arg].c_str();
cc2978dc
BH
243 mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8));
244 if (mode == 0 && cptr_ret == cptr_orig)
245 throw ArgException("'" + arg + string("' contains invalid octal mode"));
246 return mode;
247}
248
249gid_t ArgvMap::asGid(const string &arg)
250{
251 gid_t gid;
252 const char *cptr_orig;
4646277d 253 char *cptr_ret = nullptr;
cc2978dc
BH
254
255 if(!parmIsset(arg))
256 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
257
9d6e970b 258 cptr_orig = d_params[arg].c_str();
cc2978dc
BH
259 gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0));
260 if (gid == 0 && cptr_ret == cptr_orig) {
261 // try to resolve
9d6e970b 262 struct group *group = getgrnam(d_params[arg].c_str());
4646277d 263 if (group == nullptr)
cc2978dc
BH
264 throw ArgException("'" + arg + string("' contains invalid group"));
265 gid = group->gr_gid;
266 }
267 return gid;
268}
269
270uid_t ArgvMap::asUid(const string &arg)
271{
272 uid_t uid;
273 const char *cptr_orig;
4646277d 274 char *cptr_ret = nullptr;
cc2978dc
BH
275
276 if(!parmIsset(arg))
277 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
278
9d6e970b 279 cptr_orig = d_params[arg].c_str();
cc2978dc
BH
280 uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
281 if (uid == 0 && cptr_ret == cptr_orig) {
282 // try to resolve
9d6e970b 283 struct passwd *pwent = getpwnam(d_params[arg].c_str());
4646277d 284 if (pwent == nullptr)
cc2978dc
BH
285 throw ArgException("'" + arg + string("' contains invalid group"));
286 uid = pwent->pw_uid;
287 }
288 return uid;
289}
cc2978dc 290
ff99a74b 291int ArgvMap::asNum(const string &arg, int def)
12c86877 292{
cc2978dc
BH
293 int retval;
294 const char *cptr_orig;
4646277d 295 char *cptr_ret = nullptr;
cc2978dc 296
12c86877
BH
297 if(!parmIsset(arg))
298 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
299
ff99a74b 300 // use default for empty values
9d6e970b 301 if (d_params[arg].empty())
ff99a74b 302 return def;
cc2978dc 303
9d6e970b 304 cptr_orig = d_params[arg].c_str();
cc2978dc
BH
305 retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
306 if (!retval && cptr_ret == cptr_orig)
fd378ba9 307 throw ArgException("'"+arg+"' value '"+string(cptr_orig) + string( "' is not a valid number"));
cc2978dc
BH
308
309 return retval;
310}
311
312bool ArgvMap::isEmpty(const string &arg)
313{
314 if(!parmIsset(arg))
315 return true;
9d6e970b 316 return d_params[arg].empty();
12c86877
BH
317}
318
319double ArgvMap::asDouble(const string &arg)
320{
cc2978dc
BH
321 double retval;
322 const char *cptr_orig;
4646277d 323 char *cptr_ret = nullptr;
cc2978dc 324
12c86877
BH
325 if(!parmIsset(arg))
326 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
327
9d6e970b 328 if (d_params[arg].empty())
cc2978dc
BH
329 return 0.0;
330
9d6e970b 331 cptr_orig = d_params[arg].c_str();
cc2978dc
BH
332 retval = strtod(cptr_orig, &cptr_ret);
333
334 if (retval == 0 && cptr_ret == cptr_orig)
335 throw ArgException("'"+arg+string("' is not valid double"));
336
337 return retval;
12c86877
BH
338}
339
340ArgvMap::ArgvMap()
341{
9d6e970b 342 set("ignore-unknown-settings","Configuration settings to ignore if they are unknown")="";
12c86877
BH
343}
344
345bool ArgvMap::parmIsset(const string &var)
346{
9d6e970b 347 return d_params.find(var) != d_params.end();
12c86877
BH
348}
349
3d324e00
O
350// ATM Shared between Recursor and Auth, is that a good idea?
351static const map<string,string> deprecateList = {
352 { "stats-api-blacklist", "stats-api-disabled-list" },
353 { "stats-carbon-blacklist", "stats-carbon-disabled-list" },
354 { "stats-rec-control-blacklist", "stats-rec-control-disabled-list" },
355 { "stats-snmp-blacklist", "stats-snmp-disabled-list" },
356 { "edns-subnet-whitelist", "edns-subnet-allow-list" },
357 { "new-domain-whitelist", "new-domain-ignore-list" },
358 { "snmp-master-socket", "snmp-daemon-socket" }
359};
360
ebaffe64 361void ArgvMap::warnIfDeprecated(const string& var)
3d324e00
O
362{
363 const auto msg = deprecateList.find(var);
364 if (msg != deprecateList.end()) {
ebaffe64
OM
365 SLOG(g_log << Logger::Warning << "'" << var << "' is deprecated and will be removed in a future release, use '" << msg->second << "' instead" << endl,
366 d_log->info(Logr::Warning, "Option is deprecated and will be removed in a future release", "deprecatedName", Logging::Loggable(var), "name", Logging::Loggable(msg->second)));
3d324e00
O
367 }
368}
369
12c86877
BH
370void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax)
371{
372 string var, val;
bdf40704 373 string::size_type pos;
5a177409 374 bool incremental = false;
4d752b05 375
a350fc7a 376 if(arg.find("--") == 0 && (pos=arg.find("+="))!=string::npos) // this is a --port+=25 case
4d752b05
KM
377 {
378 var=arg.substr(2,pos-2);
379 val=arg.substr(pos+2);
380 incremental = true;
381 }
2e321dad 382 else if(arg.find("--") == 0 && (pos=arg.find('='))!=string::npos) // this is a --port=25 case
4d752b05
KM
383 {
384 var=arg.substr(2,pos-2);
385 val=arg.substr(pos+1);
386 }
2e321dad 387 else if(arg.find("--") == 0 && (arg.find('=')==string::npos)) // this is a --daemon case
4d752b05
KM
388 {
389 var=arg.substr(2);
390 val="";
391 }
0e4bbf5d 392 else if(arg[0]=='-' && arg.length() > 1)
4d752b05
KM
393 {
394 var=arg.substr(1);
395 val="";
12c86877 396 }
4d752b05
KM
397 else // command
398 d_cmds.push_back(arg);
c63f0951
AT
399
400 boost::trim(var);
12c86877
BH
401
402 if(var!="" && (parseOnly.empty() || var==parseOnly)) {
ebaffe64
OM
403 if (!lax) {
404 warnIfDeprecated(var);
405 }
bd11bd1d 406 pos=val.find_first_not_of(" \t"); // strip leading whitespace
3752660e 407 if(pos && pos!=string::npos)
bd11bd1d 408 val=val.substr(pos);
4d752b05
KM
409 if(parmIsset(var))
410 {
411 if(incremental)
412 {
9d6e970b 413 if(d_params[var].empty())
4d752b05
KM
414 {
415 if(!d_cleared.count(var))
9d6e970b
CH
416 throw ArgException("Incremental setting '"+var+"' without a parent");
417 d_params[var] = val;
4d752b05
KM
418 }
419 else
9d6e970b 420 d_params[var] += ", " + val;
4d752b05
KM
421 }
422 else
423 {
9d6e970b 424 d_params[var] = val;
4d752b05 425 d_cleared.insert(var);
5a177409 426 }
3702b428 427 }
9d6e970b
CH
428 else
429 {
430 // unknown setting encountered. see if its on the ignore list before throwing.
6e01be56 431 vector<string> parts;
9d6e970b 432 stringtok(parts, d_params["ignore-unknown-settings"], " ,\t\n\r");
6e01be56 433 if (find(parts.begin(), parts.end(), var) != parts.end()) {
9d6e970b 434 d_unknownParams[var] = val;
ebaffe64
OM
435 SLOG(g_log<<Logger::Warning<<"Ignoring unknown setting '"<<var<<"' as requested"<<endl,
436 d_log->info(Logr::Warning, "Ignoring unknown setting as requested", "name", Logging::Loggable(var)));
9d6e970b
CH
437 return;
438 }
439
440 if (!lax) {
441 throw ArgException("Trying to set unknown setting '"+var+"'");
442 }
443 }
12c86877
BH
444 }
445}
446
447const vector<string>&ArgvMap::getCommands()
448{
449 return d_cmds;
450}
451
452void ArgvMap::parse(int &argc, char **argv, bool lax)
453{
116cda91 454 d_cmds.clear();
58a57699 455 d_cleared.clear();
12c86877
BH
456 for(int n=1;n<argc;n++) {
457 parseOne(argv[n],"",lax);
458 }
459}
460
461void ArgvMap::preParse(int &argc, char **argv, const string &arg)
462{
463 for(int n=1;n<argc;n++) {
464 string varval=argv[n];
a350fc7a 465 if(varval.find("--"+arg) == 0)
12c86877
BH
466 parseOne(argv[n]);
467 }
468}
469
d3002a04
AT
470bool ArgvMap::parseFile(const char *fname, const string& arg, bool lax) {
471 string line;
07b7e0b5
AT
472 string pline;
473 string::size_type pos;
474
d3002a04
AT
475 ifstream f(fname);
476 if(!f)
477 return false;
07b7e0b5 478
d3002a04 479 while(getline(f,pline)) {
dc593046 480 boost::trim_right(pline);
d3002a04 481
83281a74 482 if(!pline.empty() && pline[pline.size()-1]=='\\') {
d3002a04
AT
483 line+=pline.substr(0,pline.length()-1);
484 continue;
485 }
486 else
487 line+=pline;
488
489 // strip everything after a #
2e321dad 490 if((pos=line.find('#'))!=string::npos) {
d3002a04
AT
491 // make sure it's either first char or has whitespace before
492 // fixes issue #354
493 if (pos == 0 || std::isspace(line[pos-1]))
494 line=line.substr(0,pos);
495 }
496
497 // strip trailing spaces
dc593046 498 boost::trim_right(line);
07b7e0b5 499
d3002a04
AT
500 // strip leading spaces
501 if((pos=line.find_first_not_of(" \t\r\n"))!=string::npos)
502 line=line.substr(pos);
07b7e0b5 503
d3002a04
AT
504 // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd
505
506 parseOne( string("--") + line, arg, lax );
507 line="";
508 }
07b7e0b5 509
07b7e0b5
AT
510 return true;
511}
512
513
f64de501 514bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault)
12c86877 515{
9d6e970b 516 d_params[arg] = theDefault;
b56ac6fd 517
d3002a04 518 return parseFile(fname, arg, false);
12c86877
BH
519}
520
12c86877 521bool ArgvMap::file(const char *fname, bool lax)
4f6ef75f
AT
522{
523 return file(fname,lax,false);
524}
525
526bool ArgvMap::file(const char *fname, bool lax, bool included)
12c86877 527{
8a16d778 528 if (!parmIsset("include-dir")) // inject include-dir
4f6ef75f
AT
529 set("include-dir","Directory to include configuration files from");
530
d3002a04 531 if(!parseFile(fname, "", lax)) {
ebaffe64
OM
532 SLOG(g_log << Logger::Warning << "Unable to open " << fname << std::endl,
533 d_log->error(Logr::Warning, "Unable to open", "name", Logging::Loggable(fname)));
d3002a04
AT
534 return false;
535 }
eefd15f9 536
4f6ef75f 537 // handle include here (avoid re-include)
9d6e970b 538 if (!included && !d_params["include-dir"].empty()) {
93d7e233
AT
539 std::vector<std::string> extraConfigs;
540 gatherIncludes(extraConfigs);
ef7cd021 541 for(const std::string& fn : extraConfigs) {
93d7e233 542 if (!file(fn.c_str(), lax, true)) {
ebaffe64
OM
543 SLOG(g_log << Logger::Error << fn << " could not be parsed" << std::endl,
544 d_log->info(Logr::Error, "Could not be parsed", "name", Logging::Loggable(fn)));
93d7e233 545 throw ArgException(fn + " could not be parsed");
4f6ef75f 546 }
93d7e233 547 }
93d7e233 548 }
8a16d778 549
93d7e233
AT
550 return true;
551}
8a16d778 552
93d7e233
AT
553void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) {
554 extraConfigs.clear();
9d6e970b 555 if (d_params["include-dir"].empty())
a73da04b
OM
556 return; // nothing to do
557
558 DIR *dir;
9d6e970b 559 if (!(dir = opendir(d_params["include-dir"].c_str()))) {
a73da04b 560 int err = errno;
9d6e970b 561 string msg = d_params["include-dir"] + " is not accessible: " + strerror(err);
ebaffe64
OM
562 SLOG(g_log << Logger::Error << msg << std::endl,
563 d_log->error(Logr::Error, err, "Directory is not accessible", "name", Logging::Loggable(d_params["include-dir"])));
a73da04b
OM
564 throw ArgException(msg);
565 }
93d7e233 566
a73da04b 567 struct dirent *ent;
4646277d 568 while ((ent = readdir(dir)) != nullptr) {
a73da04b
OM
569 if (ent->d_name[0] == '.')
570 continue; // skip any dots
571 if (boost::ends_with(ent->d_name, ".conf")) {
572 // build name
9d6e970b 573 string name = d_params["include-dir"] + "/" + ent->d_name; // FIXME: Use some path separator
a73da04b
OM
574 // ensure it's readable file
575 struct stat st;
576 if (stat(name.c_str(), &st) || !S_ISREG(st.st_mode)) {
577 string msg = name + " is not a regular file";
ebaffe64
OM
578 SLOG(g_log << Logger::Error << msg << std::endl,
579 d_log->info(Logr::Error, "Is not a regular file", "name", Logging::Loggable(name)));
a73da04b
OM
580 closedir(dir);
581 throw ArgException(msg);
93d7e233 582 }
a73da04b 583 extraConfigs.push_back(name);
93d7e233 584 }
a73da04b
OM
585 }
586 std::sort(extraConfigs.begin(), extraConfigs.end(), CIStringComparePOSIX());
587 closedir(dir);
12c86877 588}