]>
Commit | Line | Data |
---|---|---|
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 |
38 | const ArgvMap::param_t::const_iterator ArgvMap::begin() |
39 | { | |
40 | return params.begin(); | |
41 | } | |
42 | ||
43 | const ArgvMap::param_t::const_iterator ArgvMap::end() | |
44 | { | |
45 | return params.end(); | |
46 | } | |
47 | ||
48 | string & ArgvMap::set(const string &var) | |
49 | { | |
50 | return params[var]; | |
51 | } | |
52 | ||
8864bdf6 KM |
53 | void ArgvMap::setDefault(const string &var, const string &value) |
54 | { | |
55 | if(! defaultmap.count(var)) | |
56 | defaultmap.insert(pair<string, string>(var, value)); | |
57 | } | |
58 | ||
59 | void ArgvMap::setDefaults() | |
60 | { | |
61 | for(map<string,string>::const_iterator i=params.begin();i!=params.end();++i) | |
62 | if(! defaultmap.count(i->first)) | |
63 | defaultmap.insert(*i); | |
64 | } | |
65 | ||
12c86877 | 66 | bool ArgvMap::mustDo(const string &var) |
bd11bd1d | 67 | { |
12c86877 BH |
68 | return ((*this)[var]!="no") && ((*this)[var]!="off"); |
69 | } | |
70 | ||
71 | vector<string>ArgvMap::list() | |
72 | { | |
73 | vector<string> ret; | |
74 | for(map<string,string>::const_iterator i=params.begin();i!=params.end();++i) | |
75 | ret.push_back(i->first); | |
76 | return ret; | |
77 | } | |
78 | ||
79 | string ArgvMap::getHelp(const string &item) | |
80 | { | |
81 | return helpmap[item]; | |
82 | } | |
83 | ||
84 | string & ArgvMap::set(const string &var, const string &help) | |
85 | { | |
86 | helpmap[var]=help; | |
87 | d_typeMap[var]="Parameter"; | |
88 | return set(var); | |
89 | } | |
90 | ||
91 | void ArgvMap::setCmd(const string &var, const string &help) | |
92 | { | |
93 | helpmap[var]=help; | |
94 | d_typeMap[var]="Command"; | |
95 | set(var)="no"; | |
96 | } | |
97 | ||
98 | string & 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 |
106 | bool ArgvMap::contains(const string &var, const string &val) |
107 | { | |
102eb646 BH |
108 | params_t::const_iterator param = params.find(var); |
109 | if(param == params.end() || param->second.empty()) { | |
110 | return false; | |
111 | } | |
562588a3 | 112 | vector<string> parts; |
562588a3 | 113 | |
102eb646 | 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 |
124 | string 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 | ||
8864bdf6 | 157 | string ArgvMap::configstring(bool running, bool full) |
12c86877 BH |
158 | { |
159 | string help; | |
6bef3d91 | 160 | |
8864bdf6 KM |
161 | if (running) |
162 | help="# Autogenerated configuration file based on running instance ("+nowTime()+")\n\n"; | |
6bef3d91 | 163 | else |
8864bdf6 KM |
164 | help="# Autogenerated configuration file template\n\n"; |
165 | ||
93a471a3 CHB |
166 | for(const auto& i: helpmap) { |
167 | if(d_typeMap[i.first]=="Command") | |
c7efa8ff RA |
168 | continue; |
169 | ||
8864bdf6 KM |
170 | if (! defaultmap.count(i.first)) { |
171 | throw ArgException(string("Default for parameter '")+i.first+"' not set"); | |
172 | } | |
173 | ||
174 | if (!running || full) { | |
175 | help+="#################################\n"; | |
176 | help+="# "; | |
177 | help+=i.first; | |
178 | help+="\t"; | |
179 | help+=i.second; | |
180 | help+="\n#\n"; | |
181 | } else { | |
182 | if (defaultmap[i.first] == params[i.first]) { | |
183 | continue; | |
184 | } | |
185 | } | |
186 | ||
187 | if (! running || defaultmap[i.first] == params[i.first]) { | |
188 | help+="# "; | |
189 | } | |
190 | ||
191 | if (running) { | |
192 | help+=i.first+"="+params[i.first]+"\n"; | |
193 | if (full) { | |
194 | help+="\n"; | |
195 | } | |
c7efa8ff | 196 | } else { |
8864bdf6 | 197 | help+=i.first+"="+defaultmap[i.first]+"\n\n"; |
12c86877 | 198 | } |
c7efa8ff | 199 | } |
12c86877 BH |
200 | return help; |
201 | } | |
202 | ||
12c86877 BH |
203 | const string & ArgvMap::operator[](const string &arg) |
204 | { | |
205 | if(!parmIsset(arg)) | |
206 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
207 | ||
12c86877 BH |
208 | return params[arg]; |
209 | } | |
210 | ||
cc2978dc BH |
211 | mode_t ArgvMap::asMode(const string &arg) |
212 | { | |
213 | mode_t mode; | |
214 | const char *cptr_orig; | |
215 | char *cptr_ret = NULL; | |
216 | ||
217 | if(!parmIsset(arg)) | |
218 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
219 | ||
220 | cptr_orig = params[arg].c_str(); | |
221 | mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8)); | |
222 | if (mode == 0 && cptr_ret == cptr_orig) | |
223 | throw ArgException("'" + arg + string("' contains invalid octal mode")); | |
224 | return mode; | |
225 | } | |
226 | ||
227 | gid_t ArgvMap::asGid(const string &arg) | |
228 | { | |
229 | gid_t gid; | |
230 | const char *cptr_orig; | |
231 | char *cptr_ret = NULL; | |
232 | ||
233 | if(!parmIsset(arg)) | |
234 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
235 | ||
236 | cptr_orig = params[arg].c_str(); | |
237 | gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0)); | |
238 | if (gid == 0 && cptr_ret == cptr_orig) { | |
239 | // try to resolve | |
240 | struct group *group = getgrnam(params[arg].c_str()); | |
241 | if (group == NULL) | |
242 | throw ArgException("'" + arg + string("' contains invalid group")); | |
243 | gid = group->gr_gid; | |
244 | } | |
245 | return gid; | |
246 | } | |
247 | ||
248 | uid_t ArgvMap::asUid(const string &arg) | |
249 | { | |
250 | uid_t uid; | |
251 | const char *cptr_orig; | |
252 | char *cptr_ret = NULL; | |
253 | ||
254 | if(!parmIsset(arg)) | |
255 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
256 | ||
257 | cptr_orig = params[arg].c_str(); | |
258 | uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0)); | |
259 | if (uid == 0 && cptr_ret == cptr_orig) { | |
260 | // try to resolve | |
261 | struct passwd *pwent = getpwnam(params[arg].c_str()); | |
262 | if (pwent == NULL) | |
263 | throw ArgException("'" + arg + string("' contains invalid group")); | |
264 | uid = pwent->pw_uid; | |
265 | } | |
266 | return uid; | |
267 | } | |
cc2978dc | 268 | |
ff99a74b | 269 | int ArgvMap::asNum(const string &arg, int def) |
12c86877 | 270 | { |
cc2978dc BH |
271 | int retval; |
272 | const char *cptr_orig; | |
273 | char *cptr_ret = NULL; | |
274 | ||
12c86877 BH |
275 | if(!parmIsset(arg)) |
276 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
277 | ||
ff99a74b | 278 | // use default for empty values |
cc2978dc | 279 | if (params[arg].empty()) |
ff99a74b | 280 | return def; |
cc2978dc BH |
281 | |
282 | cptr_orig = params[arg].c_str(); | |
283 | retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0)); | |
284 | if (!retval && cptr_ret == cptr_orig) | |
fd378ba9 | 285 | throw ArgException("'"+arg+"' value '"+string(cptr_orig) + string( "' is not a valid number")); |
cc2978dc BH |
286 | |
287 | return retval; | |
288 | } | |
289 | ||
290 | bool ArgvMap::isEmpty(const string &arg) | |
291 | { | |
292 | if(!parmIsset(arg)) | |
293 | return true; | |
294 | return params[arg].empty(); | |
12c86877 BH |
295 | } |
296 | ||
297 | double ArgvMap::asDouble(const string &arg) | |
298 | { | |
cc2978dc BH |
299 | double retval; |
300 | const char *cptr_orig; | |
301 | char *cptr_ret = NULL; | |
302 | ||
12c86877 BH |
303 | if(!parmIsset(arg)) |
304 | throw ArgException(string("Undefined but needed argument: '")+arg+"'"); | |
305 | ||
cc2978dc BH |
306 | if (params[arg].empty()) |
307 | return 0.0; | |
308 | ||
309 | cptr_orig = params[arg].c_str(); | |
310 | retval = strtod(cptr_orig, &cptr_ret); | |
311 | ||
312 | if (retval == 0 && cptr_ret == cptr_orig) | |
313 | throw ArgException("'"+arg+string("' is not valid double")); | |
314 | ||
315 | return retval; | |
12c86877 BH |
316 | } |
317 | ||
318 | ArgvMap::ArgvMap() | |
319 | { | |
320 | ||
321 | } | |
322 | ||
323 | bool ArgvMap::parmIsset(const string &var) | |
324 | { | |
325 | return (params.find(var)!=params.end()); | |
326 | } | |
327 | ||
328 | void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax) | |
329 | { | |
330 | string var, val; | |
bdf40704 | 331 | string::size_type pos; |
5a177409 | 332 | bool incremental = false; |
4d752b05 | 333 | |
a350fc7a | 334 | if(arg.find("--") == 0 && (pos=arg.find("+="))!=string::npos) // this is a --port+=25 case |
4d752b05 KM |
335 | { |
336 | var=arg.substr(2,pos-2); | |
337 | val=arg.substr(pos+2); | |
338 | incremental = true; | |
339 | } | |
a350fc7a | 340 | else if(arg.find("--") == 0 && (pos=arg.find("="))!=string::npos) // this is a --port=25 case |
4d752b05 KM |
341 | { |
342 | var=arg.substr(2,pos-2); | |
343 | val=arg.substr(pos+1); | |
344 | } | |
a350fc7a | 345 | else if(arg.find("--") == 0 && (arg.find("=")==string::npos)) // this is a --daemon case |
4d752b05 KM |
346 | { |
347 | var=arg.substr(2); | |
348 | val=""; | |
349 | } | |
12c86877 | 350 | else if(arg[0]=='-') |
4d752b05 KM |
351 | { |
352 | var=arg.substr(1); | |
353 | val=""; | |
12c86877 | 354 | } |
4d752b05 KM |
355 | else // command |
356 | d_cmds.push_back(arg); | |
c63f0951 AT |
357 | |
358 | boost::trim(var); | |
12c86877 BH |
359 | |
360 | if(var!="" && (parseOnly.empty() || var==parseOnly)) { | |
bd11bd1d | 361 | pos=val.find_first_not_of(" \t"); // strip leading whitespace |
3752660e | 362 | if(pos && pos!=string::npos) |
bd11bd1d | 363 | val=val.substr(pos); |
4d752b05 KM |
364 | if(parmIsset(var)) |
365 | { | |
366 | if(incremental) | |
367 | { | |
368 | if(params[var].empty()) | |
369 | { | |
370 | if(!d_cleared.count(var)) | |
371 | throw ArgException("Incremental parameter '"+var+"' without a parent"); | |
372 | params[var]=val; | |
373 | } | |
374 | else | |
375 | params[var]+=", " + val; | |
376 | } | |
377 | else | |
378 | { | |
379 | params[var]=val; | |
380 | d_cleared.insert(var); | |
5a177409 | 381 | } |
3702b428 | 382 | } |
4d752b05 | 383 | else if(!lax) |
a67dd0cf | 384 | throw ArgException("Trying to set unknown parameter '"+var+"'"); |
12c86877 BH |
385 | } |
386 | } | |
387 | ||
388 | const vector<string>&ArgvMap::getCommands() | |
389 | { | |
390 | return d_cmds; | |
391 | } | |
392 | ||
393 | void ArgvMap::parse(int &argc, char **argv, bool lax) | |
394 | { | |
116cda91 | 395 | d_cmds.clear(); |
58a57699 | 396 | d_cleared.clear(); |
12c86877 BH |
397 | for(int n=1;n<argc;n++) { |
398 | parseOne(argv[n],"",lax); | |
399 | } | |
400 | } | |
401 | ||
402 | void ArgvMap::preParse(int &argc, char **argv, const string &arg) | |
403 | { | |
404 | for(int n=1;n<argc;n++) { | |
405 | string varval=argv[n]; | |
a350fc7a | 406 | if(varval.find("--"+arg) == 0) |
12c86877 BH |
407 | parseOne(argv[n]); |
408 | } | |
409 | } | |
410 | ||
d3002a04 AT |
411 | bool ArgvMap::parseFile(const char *fname, const string& arg, bool lax) { |
412 | string line; | |
07b7e0b5 AT |
413 | string pline; |
414 | string::size_type pos; | |
415 | ||
d3002a04 AT |
416 | ifstream f(fname); |
417 | if(!f) | |
418 | return false; | |
07b7e0b5 | 419 | |
d3002a04 AT |
420 | while(getline(f,pline)) { |
421 | trim_right(pline); | |
422 | ||
83281a74 | 423 | if(!pline.empty() && pline[pline.size()-1]=='\\') { |
d3002a04 AT |
424 | line+=pline.substr(0,pline.length()-1); |
425 | continue; | |
426 | } | |
427 | else | |
428 | line+=pline; | |
429 | ||
430 | // strip everything after a # | |
431 | if((pos=line.find("#"))!=string::npos) { | |
432 | // make sure it's either first char or has whitespace before | |
433 | // fixes issue #354 | |
434 | if (pos == 0 || std::isspace(line[pos-1])) | |
435 | line=line.substr(0,pos); | |
436 | } | |
437 | ||
438 | // strip trailing spaces | |
439 | trim_right(line); | |
07b7e0b5 | 440 | |
d3002a04 AT |
441 | // strip leading spaces |
442 | if((pos=line.find_first_not_of(" \t\r\n"))!=string::npos) | |
443 | line=line.substr(pos); | |
07b7e0b5 | 444 | |
d3002a04 AT |
445 | // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd |
446 | ||
447 | parseOne( string("--") + line, arg, lax ); | |
448 | line=""; | |
449 | } | |
07b7e0b5 | 450 | |
07b7e0b5 AT |
451 | return true; |
452 | } | |
453 | ||
454 | ||
f64de501 | 455 | bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault) |
12c86877 | 456 | { |
f64de501 | 457 | params[arg]=theDefault; |
b56ac6fd | 458 | |
d3002a04 | 459 | return parseFile(fname, arg, false); |
12c86877 BH |
460 | } |
461 | ||
12c86877 | 462 | bool ArgvMap::file(const char *fname, bool lax) |
4f6ef75f AT |
463 | { |
464 | return file(fname,lax,false); | |
465 | } | |
466 | ||
467 | bool ArgvMap::file(const char *fname, bool lax, bool included) | |
12c86877 | 468 | { |
8a16d778 | 469 | if (!parmIsset("include-dir")) // inject include-dir |
4f6ef75f AT |
470 | set("include-dir","Directory to include configuration files from"); |
471 | ||
d3002a04 | 472 | if(!parseFile(fname, "", lax)) { |
e6a9dde5 | 473 | g_log << Logger::Warning << "Unable to open " << fname << std::endl; |
d3002a04 AT |
474 | return false; |
475 | } | |
eefd15f9 | 476 | |
4f6ef75f | 477 | // handle include here (avoid re-include) |
8a16d778 | 478 | if (!included && !params["include-dir"].empty()) { |
93d7e233 AT |
479 | std::vector<std::string> extraConfigs; |
480 | gatherIncludes(extraConfigs); | |
ef7cd021 | 481 | for(const std::string& fn : extraConfigs) { |
93d7e233 | 482 | if (!file(fn.c_str(), lax, true)) { |
e6a9dde5 | 483 | g_log << Logger::Error << fn << " could not be parsed" << std::endl; |
93d7e233 | 484 | throw ArgException(fn + " could not be parsed"); |
4f6ef75f | 485 | } |
93d7e233 | 486 | } |
93d7e233 | 487 | } |
8a16d778 | 488 | |
93d7e233 AT |
489 | return true; |
490 | } | |
8a16d778 | 491 | |
93d7e233 AT |
492 | void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) { |
493 | extraConfigs.clear(); | |
a73da04b OM |
494 | if (params["include-dir"].empty()) |
495 | return; // nothing to do | |
496 | ||
497 | DIR *dir; | |
498 | if (!(dir = opendir(params["include-dir"].c_str()))) { | |
499 | int err = errno; | |
500 | string msg = params["include-dir"] + " is not accessible: " + strerror(err); | |
501 | g_log << Logger::Error << msg << std::endl; | |
502 | throw ArgException(msg); | |
503 | } | |
93d7e233 | 504 | |
a73da04b OM |
505 | struct dirent *ent; |
506 | while ((ent = readdir(dir)) != NULL) { | |
507 | if (ent->d_name[0] == '.') | |
508 | continue; // skip any dots | |
509 | if (boost::ends_with(ent->d_name, ".conf")) { | |
510 | // build name | |
511 | string name = params["include-dir"] + "/" + ent->d_name; // FIXME: Use some path separator | |
512 | // ensure it's readable file | |
513 | struct stat st; | |
514 | if (stat(name.c_str(), &st) || !S_ISREG(st.st_mode)) { | |
515 | string msg = name + " is not a regular file"; | |
516 | g_log << Logger::Error << msg << std::endl; | |
517 | closedir(dir); | |
518 | throw ArgException(msg); | |
93d7e233 | 519 | } |
a73da04b | 520 | extraConfigs.push_back(name); |
93d7e233 | 521 | } |
a73da04b OM |
522 | } |
523 | std::sort(extraConfigs.begin(), extraConfigs.end(), CIStringComparePOSIX()); | |
524 | closedir(dir); | |
12c86877 | 525 | } |