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