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