First steps in redesign:
[bpdyndnsd] / src / config.cpp
1 /** @file
2  * @brief Config class implementation. This class represents the actual configuration.
3  *
4  *
5  *
6  * @copyright Intra2net AG
7  * @license GPLv2
8 */
9
10 #include "config.h"
11
12 #include "dhs.h"
13 #include "ods.h"
14
15 #include <time.h>
16 #include <iostream>
17 #include <fstream>
18
19 #include <boost/foreach.hpp>
20 #include <boost/filesystem.hpp>
21 #include <boost/regex.hpp>
22 #include <boost/algorithm/string.hpp>
23
24
25
26 namespace po = boost::program_options;
27 namespace fs = boost::filesystem;
28 namespace ba = boost::algorithm;
29
30 using namespace std;
31
32 /**
33  * Default Constructor. Available command line and config file options with their default values are defined here.
34  */
35 Config::Config(Logger::Ptr _log, Serviceholder::Ptr _serviceholder)
36     : DaemonMode(false)
37     , Syslog(false)
38     , EnableIPv6(false)
39     , Loglevel(0)
40     , ConfigPath("/etc/bpdyndnsd")
41 {
42     // initialize Logger
43     Log = _log;
44
45     // initialize Serviceholder
46     ServiceHolder = _serviceholder;
47
48     // Available service description config options
49     po::options_description opt_desc_service("Service description options");
50     opt_desc_service.add_options()
51         ("protocol",po::value<string>(),"The service protocol.")
52         ("host",po::value<string>(),"The hostname to update.")
53         ("login",po::value<string>(),"Login name.")
54         ("password",po::value<string>(),"Corresponding password.")
55         ("update_interval",po::value<int>()->default_value(-1),"Update interval in minutes.")
56         ("max_updates_within_interval",po::value<int>()->default_value(-1),"How many updates can be made in one interval.")
57     ;
58
59     // Available command line only options
60     po::options_description opt_desc_cmd_only("Command line only options");
61     opt_desc_cmd_only.add_options()
62         ("help,?","Show help.")
63         ("version,v","Show version.")
64         ("config,c",po::value<string>()->default_value("/etc/bpdyndnsd"),"Set the config path.")
65     ;
66
67      // Available generic options. Valid on cmd or in config file.
68     po::options_description opt_desc_generic("Generic config options");
69     opt_desc_generic.add_options()
70         ("daemon_mode",po::value<bool>()->default_value(false),"Run as system daemon.")
71         ("loglevel",po::value<int>()->default_value(0),"Loglevel.")
72         ("syslog",po::value<bool>()->default_value(false),"Use syslog facility.")
73         ("enable_ipv6",po::value<bool>()->default_value(false),"Try to use IPv6.")
74         ("webcheck_url",po::value<string>()->default_value(""),"Use this URL to determine IP.")
75         ("webcheck_url_alt",po::value<string>()->default_value(""),"Use this alternative URL to determine IP.")
76     ;
77
78     // Define valid command line parameters
79     Options_descriptionPtr _opt_desc_cmd(new po::options_description("Command line options"));
80     OptDescCmd = _opt_desc_cmd;
81     _opt_desc_cmd.reset();
82     OptDescCmd->add(opt_desc_cmd_only);
83     OptDescCmd->add(opt_desc_generic);
84     OptDescCmd->add(opt_desc_service);
85
86     // Define valid config file options
87     Options_descriptionPtr _opt_desc_conf_main(new po::options_description("Config file options"));
88     OptDescConfMain = _opt_desc_conf_main;
89     _opt_desc_conf_main.reset();
90     OptDescConfMain->add(opt_desc_generic);
91
92     // Define valid service file options
93     Options_descriptionPtr _opt_desc_conf_service(new po::options_description("Service file options"));
94     OptDescConfService = _opt_desc_conf_service;
95     _opt_desc_conf_service.reset();
96     OptDescConfService->add(opt_desc_service);
97 }
98
99
100 /**
101  * Default Destructor
102  */
103 Config::~Config()
104 {
105 }
106
107
108 /**
109  * Parses the command line arguments and does the needed actions.
110  * @param argc Command line argument number given to main.
111  * @param argv[] Pointer to command line argument array given to main.
112  * @return 0 if all is fine, -1 if not.
113  */
114 int Config::parse_cmd_line(int argc, char *argv[])
115 {
116     try
117     {
118         po::store(po::parse_command_line(argc, argv, *this->OptDescCmd), VariablesMap);
119         po::notify(VariablesMap);
120
121         if ( VariablesMap.count("help") )
122         {
123             Log->print_usage(OptDescCmd);
124             return -1;
125         }
126         else if ( VariablesMap.count("version") )
127         {
128             Log->print_version();
129             return -1;
130         }
131
132         // Create a service object if all needed options are set on the command line
133         if ( VariablesMap.count("protocol") && VariablesMap.count("host") && VariablesMap.count("login") && VariablesMap.count("password") )
134         {
135             // Get the cmd parameter values for protocol host login and password
136             string protocol = VariablesMap["protocol"].as<string>();
137             string host = VariablesMap["host"].as<string>();
138             string login = VariablesMap["login"].as<string>();
139             string password = VariablesMap["password"].as<string>();
140
141             protocol = ba::to_lower_copy(protocol);
142
143             int update_interval = 0;
144             if ( VariablesMap.count("update_interval") )
145                 update_interval = VariablesMap["update_interval"].as<int>();
146
147             int max_updates_within_interval = 0;
148             if ( VariablesMap.count("max_updates_within_interval") )
149                 max_updates_within_interval = VariablesMap["max_updates_within_interval"].as<int>();
150
151             Service::Ptr service = create_service(protocol,host,login,password,update_interval,max_updates_within_interval);
152             if ( service )
153             {
154                 ServiceHolder->add_service(service);
155                 Log->print_service_object("New Service object from command line options:", service->get_protocol(), service->get_hostname(), service->get_login() ,service->get_password(), service->get_update_interval(), service->get_max_updates_within_interval(), service->get_actual_ip(), service->get_last_updates());
156             }
157             else
158                 return -1;
159         }
160         else if ( VariablesMap.count("protocol") || VariablesMap.count("host") || VariablesMap.count("login") || VariablesMap.count("password") )
161         {
162             Log->print_missing_cmd_service_option();
163             Log->print_usage(OptDescCmd);
164             return -1;
165         }
166
167         if ( VariablesMap.count("config") )
168         {
169             fs::path full_config_path = fs::system_complete(fs::path(VariablesMap["config"].as<string>()));
170             ConfigPath = full_config_path.string();
171             if ( !fs::exists(full_config_path) ||  !fs::is_directory(full_config_path) )
172             {
173                 // Config path doesn't exist or is not a directory
174                 Log->print_error_config_path(ConfigPath);
175                 return -1;
176             }
177         }
178
179         if ( VariablesMap.count("daemon_mode") )
180             DaemonMode = VariablesMap["daemon_mode"].as<bool>();
181
182         if ( VariablesMap.count("loglevel") )
183             Loglevel = VariablesMap["loglevel"].as<int>();
184
185         if ( VariablesMap.count("syslog") )
186             Syslog = VariablesMap["syslog"].as<bool>();
187
188         if ( VariablesMap.count("enable_ipv6") )
189             EnableIPv6 = VariablesMap["enable_ipv6"].as<bool>();
190
191         if ( VariablesMap.count("webcheck_url") )
192             WebcheckIpUrl = VariablesMap["webcheck_url"].as<string>();
193
194         if ( VariablesMap.count("webcheck_url_alt") )
195             WebcheckIpUrlAlt = VariablesMap["webcheck_url_alt"].as<string>();
196
197     }
198     catch(po::unknown_option e)
199     {
200         Log->print_unknown_cmd_option(e.what());
201         Log->print_usage(OptDescCmd);
202         return -1;
203     }
204     catch(po::multiple_occurrences e)
205     {
206         Log->print_multiple_cmd_option(e.what());
207         Log->print_usage(OptDescCmd);
208         return -1;
209     }
210     return 0;
211 }
212
213
214 /**
215  * Creates a Service object from the given parameters.
216  * @param protocol Protocol to use.
217  * @param host Hostname to update.
218  * @param login Login.
219  * @param password Password.
220  * @return A pointer to the created Service object.
221  */
222 Service::Ptr Config::create_service(const string &protocol,const string &hostname, const string &login, const string &password, const int update_interval, const int max_updates_within_interval)
223 {
224     if(protocol == "dhs")
225     {
226         Service::Ptr service_dhs(new DHS(protocol,hostname,login,password,Log,update_interval,max_updates_within_interval));
227         return service_dhs;
228     }
229     else if(protocol == "ods")
230     {
231         Service::Ptr service_ods(new ODS(protocol,hostname,login,password,Log,update_interval,max_updates_within_interval));
232         return service_ods;
233     }
234     else
235     {
236         Log->print_unknown_protocol(protocol);
237         Service::Ptr service;
238         return service;
239     }
240 }
241
242
243 /**
244  * Loads a service config file, invoked by load_config_from_files.
245  * @param full_filename Filename of the service config file to load.
246  * @return 0 if all is fine, -1 otherwise.
247  */
248 int Config::load_service_config_file(const string& full_filename)
249 {
250     Log->print_load_service_conf(full_filename);
251
252     ifstream service_config_file(full_filename.c_str(),ifstream::in);
253     if(service_config_file.is_open())
254     {
255         try
256         {
257             po::variables_map vm;
258             po::parsed_options parsed_service_options = po::parse_config_file(service_config_file,*this->OptDescConfService,true);
259             po::store(parsed_service_options,vm);
260             po::notify(vm);
261
262             if(vm.count("protocol") && vm.count("host") && vm.count("login") && vm.count("password"))
263             {
264                 // create the corresponding service
265                 string protocol = vm["protocol"].as<string>();
266                 string host = vm["host"].as<string>();
267                 string login = vm["login"].as<string>();
268                 string password = vm["password"].as<string>();
269
270                 protocol = ba::to_lower_copy(protocol);
271
272                 int update_interval = 0;
273                 if ( VariablesMap.count("update_interval") )
274                     update_interval = VariablesMap["update_interval"].as<int>();
275
276                 int max_updates_within_interval = 0;
277                 if ( VariablesMap.count("max_updates_within_interval") )
278                     max_updates_within_interval = VariablesMap["max_updates_within_interval"].as<int>();
279
280                 Service::Ptr service = create_service(protocol,host,login,password,update_interval,max_updates_within_interval);
281                 if ( service )
282                 {
283                     ServiceHolder->add_service(service);
284                     Log->print_service_object("New Service object from config:", service->get_protocol(), service->get_hostname(), service->get_login() ,service->get_password(), service->get_update_interval(), service->get_max_updates_within_interval(), service->get_actual_ip(), service->get_last_updates());
285                 }
286                 else
287                     return -1;
288             }
289         }
290         catch ( po::unknown_option e )
291         {
292             // unknown option in config file detected
293             service_config_file.close();
294             Log->print_unknown_service_conf_option(e.what());
295             return -1;
296         }
297         service_config_file.close();
298     }
299     else
300     {
301         // error opening service config file for reading
302         Log->print_error_opening_r(full_filename);
303         return -1;
304     }
305     return 0;
306 }
307
308
309 /**
310  * Loads the main config file, invoked by load_config_from_files
311  * @param full_filename The full filename of the main config file to load
312  * @return 0 if all is fine, -1 otherwise
313  */
314 int Config::load_main_config_file(const string& full_filename)
315 {
316     Log->print_load_main_conf(full_filename);
317
318     ifstream main_config_file(full_filename.c_str(),ifstream::in);
319     if(main_config_file.is_open())
320     {
321         try
322         {
323             po::parsed_options parsed_main_options = po::parse_config_file(main_config_file,*this->OptDescConfMain,true);
324             po::store(parsed_main_options,VariablesMap);
325             po::notify(VariablesMap);
326
327         if ( VariablesMap.count("daemon_mode") )
328             DaemonMode = VariablesMap["daemon_mode"].as<bool>();
329
330         if ( VariablesMap.count("loglevel") )
331             Loglevel = VariablesMap["loglevel"].as<int>();
332
333         if ( VariablesMap.count("syslog") )
334             Syslog = VariablesMap["syslog"].as<bool>();
335
336         if ( VariablesMap.count("enable_ipv6") )
337             EnableIPv6 = VariablesMap["enable_ipv6"].as<bool>();
338
339         if ( VariablesMap.count("webcheck_url") )
340             WebcheckIpUrl = VariablesMap["webcheck_url"].as<string>();
341
342         if ( VariablesMap.count("webcheck_url_alt") )
343             WebcheckIpUrlAlt = VariablesMap["webcheck_url_alt"].as<string>();
344
345         }
346         catch ( po::unknown_option e )      // at the moment 04-08-2009 this exception is never thrown :-(
347         {
348             // unknown option in main config file detected
349             main_config_file.close();
350             Log->print_unknown_main_conf_option(e.what());
351             return -1;
352         }
353         main_config_file.close();
354     }
355     else
356     {
357         // error opening main config file for reading
358         Log->print_error_opening_r(full_filename);
359         return -1;
360     }
361     return 0;
362 }
363
364
365 /**
366  * Loads the main and the service config file and does the needed action.
367  * @param config_path The path to the config directory.
368  * @return 0 if all is fine, -1 otherwise
369  */
370 int Config::load_config_from_files()
371 {
372     fs::path full_config_path = fs::path(ConfigPath);
373
374     fs::directory_iterator end_iter;
375     for ( fs::directory_iterator dir_itr(full_config_path) ; dir_itr != end_iter ; ++dir_itr )
376     {
377         if( fs::is_regular_file( dir_itr->status() ) )
378         {
379             string actual_file = dir_itr->path().filename();
380             boost::regex expr(".*\\.conf?");
381              // If it is the main config file do the following
382             if ( actual_file == "bpdyndnsd.conf" )
383             {
384                 // Load the main config file
385                 string full_filename = dir_itr->path().string();
386                 if ( load_main_config_file(full_filename) != 0 )
387                     return -1;
388             }
389             // If it is a service definition file *.conf, parse it and generate the corresponding service
390             else if ( boost::regex_search( actual_file,expr ) )
391             {
392                 string full_filename = dir_itr->path().string();
393                 if ( load_service_config_file(full_filename) != 0 )
394                     return -1;
395             }
396         }
397     }
398     // Config file successfully loaded
399     Log->print_conf_loaded(ConfigPath);
400     return 0;
401 }
402
403
404 /**
405  * Getter method for member OptDescCmd.
406  * @return options_description*.
407  */
408 Config::Options_descriptionPtr Config::get_opt_desc_cmd() const
409 {
410     return OptDescCmd;
411 }
412
413
414 /**
415  * Getter method for member OptDescConfMain.
416  * @return options_description*.
417  */
418 Config::Options_descriptionPtr Config::get_opt_desc_conf_main() const
419 {
420     return OptDescConfMain;
421 }
422
423
424 /**
425  * Getter method for member OptDescConfService.
426  * @return options_description*.
427  */
428 Config::Options_descriptionPtr Config::get_opt_desc_conf_service() const
429 {
430     return OptDescConfService;
431 }
432
433
434 /**
435  * Getter for member Loglevel.
436  * @return Member Loglevel.
437  */
438 int Config::get_loglevel() const
439 {
440     return Loglevel;
441 }
442
443
444 /**
445  * Getter for member DaemonMode.
446  * @return TRUE if enabled, FALSE if disabled.
447  */
448 bool Config::get_daemon_mode() const
449 {
450     return DaemonMode;
451 }
452
453
454 /**
455  * Deletes the map with the previously parsed options.
456  * This is needed in case we reload the config and don't want the old cmd options to overwrite new config file options.
457  */
458 void Config::delete_variables_map()
459 {
460     VariablesMap.clear();
461
462     po::variables_map _variables_map;
463     VariablesMap = _variables_map;
464 }
465
466
467 /**
468  * Getter for member Syslog.
469  * @return True if logging through syslog is enabled, false otherwise.
470  */
471 bool Config::get_syslog() const
472 {
473     return Syslog;
474 }
475
476
477 /**
478  * Getter for member EnableIPv6
479  * @return Wether IPv6 should be used or not.
480  */
481 bool Config::get_enable_ipv6() const
482 {
483     return EnableIPv6;
484 }
485
486
487 /**
488  * Getter for member WebcheckIpUrl
489  * @return The primary IP Webcheck URL
490  */
491 string Config::get_webcheck_ip_url() const
492 {
493     return WebcheckIpUrl;
494 }
495
496
497 /**
498  * Getter for member WebcheckIpUrlAlt
499  * @return The alternative IP Webcheck URL
500  */
501 string Config::get_webcheck_ip_url_alt() const
502 {
503     return WebcheckIpUrlAlt;
504 }