]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/arguments.cc
be careful reading empty lines in our config parser and prevent integer overflow...
[thirdparty/pdns.git] / pdns / arguments.cc
1 /*
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002 - 2008 PowerDNS.COM BV
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 as published
7 by the Free Software Foundation
8
9 Additionally, the license of this program contains a special
10 exception which allows to distribute the program in binary form when
11 it is linked against OpenSSL.
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 St, 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 #include <boost/foreach.hpp>
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+string("' is not 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 BOOST_FOREACH(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 throw ArgException(namebuf.str() + " does not exist!");
499 }
500 extraConfigs.push_back(namebuf.str());
501 }
502 }
503 std::sort(extraConfigs.begin(), extraConfigs.end(), CIStringComparePOSIX());
504 closedir(dir);
505 }