]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/arguments.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
25 #include "arguments.hh"
26 #include <boost/algorithm/string.hpp>
27 #include <boost/algorithm/string/compare.hpp>
28 #include <boost/algorithm/string/predicate.hpp>
30 #include "namespaces.hh"
32 #include <sys/types.h>
38 ArgvMap :: param_t :: const_iterator
ArgvMap :: begin ()
40 return d_params
. begin ();
43 ArgvMap :: param_t :: const_iterator
ArgvMap :: end ()
45 return d_params
. end ();
48 string
& ArgvMap :: set ( const string
& var
)
53 void ArgvMap :: setDefault ( const string
& var
, const string
& value
)
55 if ( defaultmap
. count ( var
) == 0 ) {
56 defaultmap
. insert ( pair
< string
, string
>( var
, value
));
60 void ArgvMap :: setDefaults ()
62 for ( const auto & param
: d_params
) {
63 if ( defaultmap
. count ( param
. first
) == 0 ) {
64 defaultmap
. insert ( param
);
69 bool ArgvMap :: mustDo ( const string
& var
)
71 return ((* this )[ var
] != "no" ) && ((* this )[ var
] != "off" );
74 vector
< string
> ArgvMap :: list ()
77 ret
. reserve ( d_params
. size ());
78 for ( const auto & param
: d_params
) {
79 ret
. push_back ( param
. first
);
84 string
& ArgvMap :: set ( const string
& var
, const string
& help
)
87 d_typeMap
[ var
] = "Parameter" ;
91 void ArgvMap :: setCmd ( const string
& var
, const string
& help
)
94 d_typeMap
[ var
] = "Command" ;
98 string
& ArgvMap :: setSwitch ( const string
& var
, const string
& help
)
101 d_typeMap
[ var
] = "Switch" ;
105 bool ArgvMap :: contains ( const string
& var
, const string
& val
)
107 const auto & param
= d_params
. find ( var
);
108 if ( param
== d_params
. end () || param
-> second
. empty ()) {
111 vector
< string
> parts
;
113 stringtok ( parts
, param
-> second
, ", \t " );
114 return std :: any_of ( parts
. begin (), parts
. end (), [&]( const std :: string
& str
) { return str
== val
; });
117 string
ArgvMap :: helpstring ( string prefix
)
119 if ( prefix
== "no" ) {
125 for ( const auto & helpitem
: helpmap
) {
126 if (! prefix
. empty () && helpitem
. first
. find ( prefix
) != 0 ) { // only print items with prefix
131 help
+= helpitem
. first
;
133 string type
= d_typeMap
[ helpitem
. first
];
135 if ( type
== "Parameter" ) {
138 else if ( type
== "Switch" ) {
139 help
+= " | --" + helpitem
. first
+ "=yes" ;
140 help
+= " | --" + helpitem
. first
+ "=no" ;
144 help
+= helpitem
. second
;
150 string
ArgvMap :: formatOne ( bool running
, bool full
, const string
& var
, const string
& help
, const string
& theDefault
, const string
& current
)
154 if (! running
|| full
) {
155 out
+= "################################# \n " ;
163 if ( theDefault
== current
) {
168 if (! running
|| theDefault
== current
) {
173 out
+= var
+ "=" + current
+ " \n " ;
179 out
+= var
+ "=" + theDefault
+ " \n\n " ;
185 // If running and full, only changed settings are returned.
186 string
ArgvMap :: configstring ( bool running
, bool full
)
191 help
= "# Autogenerated configuration file based on running instance (" + nowTime () + ") \n\n " ;
194 help
= "# Autogenerated configuration file template \n\n " ;
197 // Affects parsing, should come first.
198 help
+= formatOne ( running
, full
, "ignore-unknown-settings" , helpmap
[ "ignore-unknown-settings" ], defaultmap
[ "ignore-unknown-settings" ], d_params
[ "ignore-unknown-settings" ]);
200 for ( const auto & helpitem
: helpmap
) {
201 if ( d_typeMap
[ helpitem
. first
] == "Command" ) {
204 if ( helpitem
. first
== "ignore-unknown-settings" ) {
208 if ( defaultmap
. count ( helpitem
. first
) == 0 ) {
209 throw ArgException ( string ( "Default for setting '" ) + helpitem
. first
+ "' not set" );
212 help
+= formatOne ( running
, full
, helpitem
. first
, helpitem
. second
, defaultmap
[ helpitem
. first
], d_params
[ helpitem
. first
]);
216 for ( const auto & unknown
: d_unknownParams
) {
217 help
+= formatOne ( running
, full
, unknown
. first
, "unknown setting" , "" , unknown
. second
);
224 const string
& ArgvMap :: operator []( const string
& arg
)
226 if (! parmIsset ( arg
)) {
227 throw ArgException ( string ( "Undefined but needed argument: '" ) + arg
+ "'" );
230 return d_params
[ arg
];
233 mode_t
ArgvMap :: asMode ( const string
& arg
)
235 if (! parmIsset ( arg
)) {
236 throw ArgException ( string ( "Undefined but needed argument: '" ) + arg
+ "'" );
239 const auto * const cptr_orig
= d_params
[ arg
]. c_str ();
240 char * cptr_ret
= nullptr ;
242 auto mode
= static_cast < mode_t
>( strtol ( cptr_orig
, & cptr_ret
, 8 ));
243 if ( mode
== 0 && cptr_ret
== cptr_orig
) {
244 throw ArgException ( "'" + arg
+ string ( "' contains invalid octal mode" ));
249 gid_t
ArgvMap :: asGid ( const string
& arg
)
251 if (! parmIsset ( arg
)) {
252 throw ArgException ( string ( "Undefined but needed argument: '" ) + arg
+ "'" );
255 const auto * cptr_orig
= d_params
[ arg
]. c_str ();
256 char * cptr_ret
= nullptr ;
257 auto gid
= static_cast < gid_t
>( strtol ( cptr_orig
, & cptr_ret
, 0 ));
258 if ( gid
== 0 && cptr_ret
== cptr_orig
) {
261 struct group
* group
= getgrnam ( d_params
[ arg
]. c_str ()); // NOLINT: called before going multi-threaded
262 if ( group
== nullptr ) {
263 throw ArgException ( "'" + arg
+ string ( "' contains invalid group" ));
270 uid_t
ArgvMap :: asUid ( const string
& arg
)
272 if (! parmIsset ( arg
)) {
273 throw ArgException ( string ( "Undefined but needed argument: '" ) + arg
+ "'" );
276 const auto * cptr_orig
= d_params
[ arg
]. c_str ();
277 char * cptr_ret
= nullptr ;
279 auto uid
= static_cast < uid_t
>( strtol ( cptr_orig
, & cptr_ret
, 0 ));
280 if ( uid
== 0 && cptr_ret
== cptr_orig
) {
282 struct passwd
* pwent
= getpwnam ( d_params
[ arg
]. c_str ()); // NOLINT: called before going multi-threaded
283 if ( pwent
== nullptr ) {
284 throw ArgException ( "'" + arg
+ string ( "' contains invalid group" ));
291 int ArgvMap :: asNum ( const string
& arg
, int def
)
293 if (! parmIsset ( arg
)) {
294 throw ArgException ( string ( "Undefined but needed argument: '" ) + arg
+ "'" );
297 // use default for empty values
298 if ( d_params
[ arg
]. empty ()) {
302 const auto * cptr_orig
= d_params
[ arg
]. c_str ();
303 char * cptr_ret
= nullptr ;
304 auto retval
= static_cast < int >( strtol ( cptr_orig
, & cptr_ret
, 0 ));
305 if ( retval
== 0 && cptr_ret
== cptr_orig
) {
306 throw ArgException ( "'" + arg
+ "' value '" + string ( cptr_orig
) + string ( "' is not a valid number" ));
312 bool ArgvMap :: isEmpty ( const string
& arg
)
314 if (! parmIsset ( arg
)) {
317 return d_params
[ arg
]. empty ();
320 double ArgvMap :: asDouble ( const string
& arg
)
322 if (! parmIsset ( arg
)) {
323 throw ArgException ( string ( "Undefined but needed argument: '" ) + arg
+ "'" );
326 if ( d_params
[ arg
]. empty ()) {
330 const auto * cptr_orig
= d_params
[ arg
]. c_str ();
331 char * cptr_ret
= nullptr ;
332 auto retval
= strtod ( cptr_orig
, & cptr_ret
);
334 if ( retval
== 0 && cptr_ret
== cptr_orig
) {
335 throw ArgException ( "'" + arg
+ string ( "' is not valid double" ));
343 set ( "ignore-unknown-settings" , "Configuration settings to ignore if they are unknown" ) = "" ;
346 bool ArgvMap :: parmIsset ( const string
& var
)
348 return d_params
. find ( var
) != d_params
. end ();
351 // ATM Shared between Recursor and Auth, is that a good idea?
352 static const map
< string
, string
> deprecateList
= {
353 { "stats-api-blacklist" , "stats-api-disabled-list" },
354 { "stats-carbon-blacklist" , "stats-carbon-disabled-list" },
355 { "stats-rec-control-blacklist" , "stats-rec-control-disabled-list" },
356 { "stats-snmp-blacklist" , "stats-snmp-disabled-list" },
357 { "edns-subnet-whitelist" , "edns-subnet-allow-list" },
358 { "new-domain-whitelist" , "new-domain-ignore-list" },
359 { "snmp-master-socket" , "snmp-daemon-socket" },
360 { "xpf-allow-from" , "Proxy Protocol" },
361 { "xpf-rr-code" , "Proxy Protocol" },
362 { "domain-metadata-cache-ttl" , "zone-metadata-cache-ttl" },
365 // NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
366 void ArgvMap :: warnIfDeprecated ( const string
& var
) const
368 const auto msg
= deprecateList
. find ( var
);
369 if ( msg
!= deprecateList
. end ()) {
370 SLOG ( g_log
<< Logger :: Warning
<< "'" << var
<< "' is deprecated and will be removed in a future release, use '" << msg
-> second
<< "' instead" << endl
,
371 d_log
-> info ( Logr :: Warning
, "Option is deprecated and will be removed in a future release" , "deprecatedName" , Logging :: Loggable ( var
), "alternative" , Logging :: Loggable ( msg
-> second
)));
375 string
ArgvMap :: isDeprecated ( const string
& var
)
377 const auto msg
= deprecateList
. find ( var
);
378 return msg
!= deprecateList
. end () ? msg
-> second
: "" ;
381 void ArgvMap :: parseOne ( const string
& arg
, const string
& parseOnly
, bool lax
)
385 string :: size_type pos
= 0 ;
386 bool incremental
= false ;
388 if ( arg
. find ( "--" ) == 0 && ( pos
= arg
. find ( "+=" )) != string :: npos
) // this is a --port+=25 case
390 var
= arg
. substr ( 2 , pos
- 2 );
391 val
= arg
. substr ( pos
+ 2 );
394 else if ( arg
. find ( "--" ) == 0 && ( pos
= arg
. find ( '=' )) != string :: npos
) // this is a --port=25 case
396 var
= arg
. substr ( 2 , pos
- 2 );
397 val
= arg
. substr ( pos
+ 1 );
399 else if ( arg
. find ( "--" ) == 0 && ( arg
. find ( '=' ) == string :: npos
)) // this is a --daemon case
404 else if ( arg
[ 0 ] == '-' && arg
. length () > 1 ) {
409 d_cmds
. push_back ( arg
);
414 if (! var
. empty () && ( parseOnly
. empty () || var
== parseOnly
)) {
416 warnIfDeprecated ( var
);
418 pos
= val
. find_first_not_of ( " \t " ); // strip leading whitespace
419 if ( pos
!= 0 && pos
!= string :: npos
) {
420 val
= val
. substr ( pos
);
422 if ( parmIsset ( var
)) {
424 if ( d_params
[ var
]. empty ()) {
425 if ( d_cleared
. count ( var
) == 0 ) {
426 throw ArgException ( "Incremental setting '" + var
+ "' without a parent" );
431 d_params
[ var
] += ", " + val
;
436 d_cleared
. insert ( var
);
440 // unknown setting encountered. see if its on the ignore list before throwing.
441 vector
< string
> parts
;
442 stringtok ( parts
, d_params
[ "ignore-unknown-settings" ], " , \t\n\r " );
443 if ( find ( parts
. begin (), parts
. end (), var
) != parts
. end ()) {
444 d_unknownParams
[ var
] = std :: move ( val
);
445 SLOG ( g_log
<< Logger :: Warning
<< "Ignoring unknown setting '" << var
<< "' as requested" << endl
,
446 d_log
-> info ( Logr :: Warning
, "Ignoring unknown setting as requested" , "name" , Logging :: Loggable ( var
)));
451 throw ArgException ( "Trying to set unknown setting '" + var
+ "'" );
457 const vector
< string
>& ArgvMap :: getCommands ()
462 void ArgvMap :: parse ( int & argc
, char ** argv
, bool lax
)
466 for ( int i
= 1 ; i
< argc
; i
++) {
467 parseOne ( argv
[ i
], "" , lax
); // NOLINT: Posix argument parsing
471 void ArgvMap :: preParse ( int & argc
, char ** argv
, const string
& arg
)
473 for ( int i
= 1 ; i
< argc
; i
++) {
474 string varval
= argv
[ i
]; // NOLINT: Posix argument parsing
475 if ( varval
. find ( "--" + arg
) == 0 ) {
476 parseOne ( argv
[ i
]); // NOLINT: Posix argument parsing
481 bool ArgvMap :: parseFile ( const string
& fname
, const string
& arg
, bool lax
)
486 std :: ifstream
configFileStream ( fname
);
487 if (! configFileStream
) {
491 while ( getline ( configFileStream
, pline
)) {
492 boost :: trim_right ( pline
);
494 if (! pline
. empty () && pline
[ pline
. size () - 1 ] == ' \\ ' ) {
495 line
+= pline
. substr ( 0 , pline
. length () - 1 );
501 // strip everything after a #
502 string :: size_type pos
= line
. find ( '#' );
503 if ( pos
!= string :: npos
) {
504 // make sure it's either first char or has whitespace before
506 if ( pos
== 0 || ( std :: isspace ( line
[ pos
- 1 ]) != 0 )) {
507 line
= line
. substr ( 0 , pos
);
511 // strip trailing spaces
512 boost :: trim_right ( line
);
514 // strip leading spaces
515 pos
= line
. find_first_not_of ( " \t\r\n " );
516 if ( pos
!= string :: npos
) {
517 line
= line
. substr ( pos
);
520 // gpgsql-basic-query=sdfsdfs dfsdfsdf sdfsdfsfd
522 parseOne ( string ( "--" ) + line
, arg
, lax
);
529 bool ArgvMap :: preParseFile ( const string
& fname
, const string
& arg
, const string
& theDefault
)
531 d_params
[ arg
] = theDefault
;
533 return parseFile ( fname
, arg
, false );
536 bool ArgvMap :: file ( const string
& fname
, bool lax
)
538 return file ( fname
, lax
, false );
541 bool ArgvMap :: file ( const string
& fname
, bool lax
, bool included
)
543 if (! parmIsset ( "include-dir" )) { // inject include-dir
544 set ( "include-dir" , "Directory to include configuration files from" );
547 if (! parseFile ( fname
, "" , lax
)) {
548 SLOG ( g_log
<< Logger :: Warning
<< "Unable to open " << fname
<< std :: endl
,
549 d_log
-> error ( Logr :: Warning
, "Unable to open file" , "name" , Logging :: Loggable ( fname
)));
553 // handle include here (avoid re-include)
554 if (! included
&& ! d_params
[ "include-dir" ]. empty ()) {
555 std :: vector
< std :: string
> extraConfigs
;
556 gatherIncludes ( d_params
[ "include-dir" ], ".conf" , extraConfigs
);
557 for ( const std :: string
& filename
: extraConfigs
) {
558 if (! file ( filename
. c_str (), lax
, true )) {
559 SLOG ( g_log
<< Logger :: Error
<< filename
<< " could not be parsed" << std :: endl
,
560 d_log
-> info ( Logr :: Error
, "Unable to parse config file" , "name" , Logging :: Loggable ( filename
)));
561 throw ArgException ( filename
+ " could not be parsed" );
569 // NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
570 void ArgvMap :: gatherIncludes ( const std :: string
& directory
, const std :: string
& suffix
, std :: vector
< std :: string
>& extraConfigs
)
572 if ( directory
. empty ()) {
573 return ; // nothing to do
576 std :: vector
< std :: string
> vec
;
577 auto directoryError
= pdns :: visit_directory ( directory
, [ this , & directory
, & suffix
, & vec
]([[ maybe_unused
]] ino_t inodeNumber
, const std :: string_view
& name
) {
579 if ( boost :: starts_with ( name
, "." )) {
580 return true ; // skip any dots
582 if ( boost :: ends_with ( name
, suffix
)) {
584 string fullName
= directory
+ "/" + std :: string ( name
);
585 // ensure it's readable file
589 if ( stat ( fullName
. c_str (), & statInfo
) != 0 || ! S_ISREG ( statInfo
. st_mode
)) {
590 string msg
= fullName
+ " is not a regular file" ;
591 SLOG ( g_log
<< Logger :: Error
<< msg
<< std :: endl
,
592 d_log
-> info ( Logr :: Error
, "Unable to open non-regular file" , "name" , Logging :: Loggable ( fullName
)));
593 throw ArgException ( msg
);
595 vec
. emplace_back ( fullName
);
600 if ( directoryError
) {
602 string msg
= directory
+ " is not accessible: " + stringerror ( err
);
603 SLOG ( g_log
<< Logger :: Error
<< msg
<< std :: endl
,
604 d_log
-> error ( Logr :: Error
, err
, "Directory is not accessible" , "name" , Logging :: Loggable ( directory
)));
605 throw ArgException ( msg
);
608 std :: sort ( vec
. begin (), vec
. end (), CIStringComparePOSIX ());
609 extraConfigs
. insert ( extraConfigs
. end (), vec
. begin (), vec
. end ());