]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/arguments.cc
dnsdist: Add HTTPStatusAction to return a specific HTTP response
[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
101 stringtok( parts, param->second, ", \t" );
102 for (const auto& part: parts) {
103 if (part == val) {
104 return true;
105 }
106 }
107
108 return false;
109 }
110
111 string ArgvMap::helpstring(string prefix)
112 {
113 if(prefix=="no")
114 prefix="";
115
116 string help;
117
118 for (const auto& i: helpmap) {
119 if(!prefix.empty() && i.first.find(prefix) != 0) // only print items with prefix
120 continue;
121
122 help+=" --";
123 help+=i.first;
124
125 string type=d_typeMap[i.first];
126
127 if(type=="Parameter")
128 help+="=...";
129 else if(type=="Switch")
130 {
131 help+=" | --"+i.first+"=yes";
132 help+=" | --"+i.first+"=no";
133 }
134
135
136 help+="\n\t";
137 help+=i.second;
138 help+="\n";
139
140 }
141 return help;
142 }
143
144 string ArgvMap::configstring(bool current)
145 {
146 string help;
147
148 if (current)
149 help="# Autogenerated configuration file based on running instance\n";
150 else
151 help="# Autogenerated configuration file template\n";
152
153 for(const auto& i: helpmap) {
154 if(d_typeMap[i.first]=="Command")
155 continue;
156
157 help+="#################################\n";
158 help+="# ";
159 help+=i.first;
160 help+="\t";
161 help+=i.second;
162 help+="\n#\n";
163 if (current) {
164 help+=i.first+"="+params[i.first]+"\n\n";
165 } else {
166 help+="# "+i.first+"="+params[i.first]+"\n\n";
167 }
168 }
169 return help;
170 }
171
172 const string & ArgvMap::operator[](const string &arg)
173 {
174 if(!parmIsset(arg))
175 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
176
177 return params[arg];
178 }
179
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 }
237
238 int ArgvMap::asNum(const string &arg, int def)
239 {
240 int retval;
241 const char *cptr_orig;
242 char *cptr_ret = NULL;
243
244 if(!parmIsset(arg))
245 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
246
247 // use default for empty values
248 if (params[arg].empty())
249 return def;
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)
254 throw ArgException("'"+arg+"' value '"+string(cptr_orig) + string( "' is not a valid number"));
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();
264 }
265
266 double ArgvMap::asDouble(const string &arg)
267 {
268 double retval;
269 const char *cptr_orig;
270 char *cptr_ret = NULL;
271
272 if(!parmIsset(arg))
273 throw ArgException(string("Undefined but needed argument: '")+arg+"'");
274
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;
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;
300 string::size_type pos;
301 bool incremental = false;
302
303 if(arg.find("--") == 0 && (pos=arg.find("+="))!=string::npos) // this is a --port+=25 case
304 {
305 var=arg.substr(2,pos-2);
306 val=arg.substr(pos+2);
307 incremental = true;
308 }
309 else if(arg.find("--") == 0 && (pos=arg.find("="))!=string::npos) // this is a --port=25 case
310 {
311 var=arg.substr(2,pos-2);
312 val=arg.substr(pos+1);
313 }
314 else if(arg.find("--") == 0 && (arg.find("=")==string::npos)) // this is a --daemon case
315 {
316 var=arg.substr(2);
317 val="";
318 }
319 else if(arg[0]=='-')
320 {
321 var=arg.substr(1);
322 val="";
323 }
324 else // command
325 d_cmds.push_back(arg);
326
327 boost::trim(var);
328
329 if(var!="" && (parseOnly.empty() || var==parseOnly)) {
330 pos=val.find_first_not_of(" \t"); // strip leading whitespace
331 if(pos && pos!=string::npos)
332 val=val.substr(pos);
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);
350 }
351 }
352 else if(!lax)
353 throw ArgException("Trying to set unknown parameter '"+var+"'");
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 {
364 d_cmds.clear();
365 d_cleared.clear();
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];
375 if(varval.find("--"+arg) == 0)
376 parseOne(argv[n]);
377 }
378 }
379
380 bool ArgvMap::parseFile(const char *fname, const string& arg, bool lax) {
381 string line;
382 string pline;
383 string::size_type pos;
384
385 ifstream f(fname);
386 if(!f)
387 return false;
388
389 while(getline(f,pline)) {
390 trim_right(pline);
391
392 if(!pline.empty() && pline[pline.size()-1]=='\\') {
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);
409
410 // strip leading spaces
411 if((pos=line.find_first_not_of(" \t\r\n"))!=string::npos)
412 line=line.substr(pos);
413
414 // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd
415
416 parseOne( string("--") + line, arg, lax );
417 line="";
418 }
419
420 return true;
421 }
422
423
424 bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault)
425 {
426 params[arg]=theDefault;
427
428 return parseFile(fname, arg, false);
429 }
430
431 bool ArgvMap::file(const char *fname, bool lax)
432 {
433 return file(fname,lax,false);
434 }
435
436 bool ArgvMap::file(const char *fname, bool lax, bool included)
437 {
438 if (!parmIsset("include-dir")) // inject include-dir
439 set("include-dir","Directory to include configuration files from");
440
441 if(!parseFile(fname, "", lax)) {
442 g_log << Logger::Warning << "Unable to open " << fname << std::endl;
443 return false;
444 }
445
446 // handle include here (avoid re-include)
447 if (!included && !params["include-dir"].empty()) {
448 std::vector<std::string> extraConfigs;
449 gatherIncludes(extraConfigs);
450 for(const std::string& fn : extraConfigs) {
451 if (!file(fn.c_str(), lax, true)) {
452 g_log << Logger::Error << fn << " could not be parsed" << std::endl;
453 throw ArgException(fn + " could not be parsed");
454 }
455 }
456 }
457
458 return true;
459 }
460
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)) {
470 g_log << Logger::Error << params["include-dir"] << " does not exist!" << std::endl;
471 throw ArgException(params["include-dir"] + " does not exist!");
472 }
473
474 // wonder if it's accessible directory
475 if (!S_ISDIR(st.st_mode)) {
476 g_log << Logger::Error << params["include-dir"] << " is not a directory" << std::endl;
477 throw ArgException(params["include-dir"] + " is not a directory");
478 }
479
480 if (!(dir = opendir(params["include-dir"].c_str()))) {
481 g_log << Logger::Error << params["include-dir"] << " is not accessible" << std::endl;
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)) {
493 g_log << Logger::Error << namebuf.str() << " is not a file" << std::endl;
494 closedir(dir);
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());
501 closedir(dir);
502 }