Added implementation of dyns service.
[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 #include "dyndns.h"
15 #include "dyns.h"
16
17 #include <time.h>
18 #include <iostream>
19 #include <fstream>
20
21 #include <boost/foreach.hpp>
22 #include <boost/filesystem.hpp>
23 #include <boost/regex.hpp>
24 #include <boost/algorithm/string.hpp>
25
26
27
28 namespace po = boost::program_options;
29 namespace fs = boost::filesystem;
30 namespace ba = boost::algorithm;
31
32 using namespace std;
33
34 /**
35  * Default Constructor. Available command line and config file options with their default values are defined here.
36  */
37 Config::Config(Logger::Ptr _log, Serviceholder::Ptr _serviceholder)
38     : Log(_log)
39     , ServiceHolder(_serviceholder)
40     , DaemonMode(false)
41     , Syslog(false)
42     , EnableIPv6(false)
43     , Loglevel(0)
44     , ConfigPath("/etc/bpdyndnsd")
45     , ExternalWarningLog("")
46     , ExternalWarningLevel(0)
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         ("dns_cache_ttl",po::value<int>()->default_value(-1),"How long a dns record is valid.")
58     ;
59
60     // Available command line only options
61     po::options_description opt_desc_cmd_only("Command line only options");
62     opt_desc_cmd_only.add_options()
63         ("help,?","Show help.")
64         ("version,v","Show version.")
65         ("config,c",po::value<string>()->default_value("/etc/bpdyndnsd"),"Set the config path.")
66     ;
67
68      // Available generic options. Valid on cmd or in config file.
69     po::options_description opt_desc_generic("Generic config options");
70     opt_desc_generic.add_options()
71         ("daemon_mode",po::value<bool>()->default_value(false),"Run as system daemon.")
72         ("loglevel",po::value<int>()->default_value(0),"Loglevel.")
73         ("syslog",po::value<bool>()->default_value(false),"Use syslog facility.")
74         ("enable_ipv6",po::value<bool>()->default_value(false),"Try to use IPv6.")
75         ("webcheck_url",po::value<string>()->default_value(""),"Use this URL to determine IP.")
76         ("webcheck_url_alt",po::value<string>()->default_value(""),"Use this alternative URL to determine IP.")
77         ("http_proxy",po::value<string>(),"Use this proxy for all http requests.")
78         ("http_proxy_port",po::value<int>(),"Port of the proxy.")
79         ("external_warning_log",po::value<string>()->default_value(""),"External programm to pass warning log messages to.")
80         ("external_warning_level",po::value<int>()->default_value(0),"Warning messages of which loglevel should be passed to external programm.")
81     ;
82
83     // Define valid command line parameters
84     Options_descriptionPtr _opt_desc_cmd(new po::options_description("Command line options"));
85     OptDescCmd = _opt_desc_cmd;
86     _opt_desc_cmd.reset();
87     OptDescCmd->add(opt_desc_cmd_only);
88     OptDescCmd->add(opt_desc_generic);
89     OptDescCmd->add(opt_desc_service);
90
91     // Define valid config file options
92     Options_descriptionPtr _opt_desc_conf_main(new po::options_description("Config file options"));
93     OptDescConfMain = _opt_desc_conf_main;
94     _opt_desc_conf_main.reset();
95     OptDescConfMain->add(opt_desc_generic);
96
97     // Define valid service file options
98     Options_descriptionPtr _opt_desc_conf_service(new po::options_description("Service file options"));
99     OptDescConfService = _opt_desc_conf_service;
100     _opt_desc_conf_service.reset();
101     OptDescConfService->add(opt_desc_service);
102 }
103
104
105 /**
106  * Default Destructor
107  */
108 Config::~Config()
109 {
110 }
111
112
113 /**
114  * Parses the command line arguments and does the needed actions.
115  * @param argc Command line argument number given to main.
116  * @param argv[] Pointer to command line argument array given to main.
117  * @return 0 if all is fine, -1 if not.
118  */
119 int Config::parse_cmd_line(int argc, char *argv[])
120 {
121     try
122     {
123         po::store(po::parse_command_line(argc, argv, *this->OptDescCmd), VariablesMap);
124         po::notify(VariablesMap);
125
126         if ( VariablesMap.count("help") )
127         {
128             Log->print_usage(OptDescCmd);
129             return -1;
130         }
131         else if ( VariablesMap.count("version") )
132         {
133             Log->print_version();
134             return -1;
135         }
136
137         // Create a service object if all needed options are set on the command line
138         if ( VariablesMap.count("protocol") && VariablesMap.count("host") && VariablesMap.count("login") && VariablesMap.count("password") )
139         {
140             // Get the cmd parameter values for protocol host login and password
141             string protocol = VariablesMap["protocol"].as<string>();
142             string host = VariablesMap["host"].as<string>();
143             string login = VariablesMap["login"].as<string>();
144             string password = VariablesMap["password"].as<string>();
145
146             protocol = ba::to_lower_copy(protocol);
147
148             int update_interval = 0;
149             if ( VariablesMap.count("update_interval") )
150                 update_interval = VariablesMap["update_interval"].as<int>();
151
152             int max_updates_within_interval = 0;
153             if ( VariablesMap.count("max_updates_within_interval") )
154                 max_updates_within_interval = VariablesMap["max_updates_within_interval"].as<int>();
155
156             int dns_cache_ttl = 0;
157             if ( VariablesMap.count("dns_cache_ttl") )
158                 dns_cache_ttl = VariablesMap["dns_cache_ttl"].as<int>();
159
160             Service::Ptr service = create_service(protocol,host,login,password,update_interval,max_updates_within_interval,dns_cache_ttl);
161             if ( service )
162             {
163                 ServiceHolder->add_service(service);
164                 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_dns_cache_ttl() , service->get_actual_ip(), service->get_last_updates());
165             }
166             else
167                 return -1;
168         }
169         else if ( VariablesMap.count("protocol") || VariablesMap.count("host") || VariablesMap.count("login") || VariablesMap.count("password") )
170         {
171             Log->print_missing_cmd_service_option();
172             Log->print_usage(OptDescCmd);
173             return -1;
174         }
175
176         if ( VariablesMap.count("config") )
177         {
178             fs::path full_config_path = fs::system_complete(fs::path(VariablesMap["config"].as<string>()));
179             ConfigPath = full_config_path.string();
180             if ( !fs::exists(full_config_path) ||  !fs::is_directory(full_config_path) )
181             {
182                 // Config path doesn't exist or is not a directory
183                 Log->print_error_config_path(ConfigPath);
184                 return -1;
185             }
186         }
187
188         if ( VariablesMap.count("daemon_mode") )
189             DaemonMode = VariablesMap["daemon_mode"].as<bool>();
190
191         if ( VariablesMap.count("loglevel") )
192             Loglevel = VariablesMap["loglevel"].as<int>();
193
194         if ( VariablesMap.count("syslog") )
195             Syslog = VariablesMap["syslog"].as<bool>();
196
197         if ( VariablesMap.count("enable_ipv6") )
198             EnableIPv6 = VariablesMap["enable_ipv6"].as<bool>();
199
200         if ( VariablesMap.count("webcheck_url") )
201             WebcheckIpUrl = VariablesMap["webcheck_url"].as<string>();
202
203         if ( VariablesMap.count("webcheck_url_alt") )
204             WebcheckIpUrlAlt = VariablesMap["webcheck_url_alt"].as<string>();
205
206         if ( VariablesMap.count("http_proxy") && VariablesMap.count("http_proxy_port") )
207         {
208             Proxy = VariablesMap["http_proxy"].as<string>();
209             ProxyPort = VariablesMap["http_proxy_port"].as<int>();
210         }
211         else if ( VariablesMap.count("http_proxy") || VariablesMap.count("http_proxy_port") )
212         {
213             Log->print_missing_cmd_proxy_option();
214             Log->print_usage(OptDescCmd);
215             return -1;
216         }
217
218         if ( VariablesMap.count("external_warning_log") )
219             ExternalWarningLog = VariablesMap["external_warning_log"].as<string>();
220
221         if ( VariablesMap.count("external_warning_level") )
222             ExternalWarningLevel = VariablesMap["external_warning_level"].as<int>();
223
224     }
225     catch(po::unknown_option e)
226     {
227         Log->print_unknown_cmd_option(e.what());
228         Log->print_usage(OptDescCmd);
229         return -1;
230     }
231     catch(po::multiple_occurrences e)
232     {
233         Log->print_multiple_cmd_option(e.what());
234         Log->print_usage(OptDescCmd);
235         return -1;
236     }
237     return 0;
238 }
239
240
241 /**
242  * Creates a Service object from the given parameters.
243  * @param protocol Protocol to use.
244  * @param host Hostname to update.
245  * @param login Login.
246  * @param password Password.
247  * @return A pointer to the created Service object.
248  */
249 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, const int dns_cache_ttl)
250 {
251     if(protocol == "dhs")
252     {
253         Service::Ptr service_dhs(new DHS(protocol,hostname,login,password,Log,update_interval,max_updates_within_interval,dns_cache_ttl,Proxy,ProxyPort));
254         return service_dhs;
255     }
256     else if(protocol == "ods")
257     {
258         Service::Ptr service_ods(new ODS(protocol,hostname,login,password,Log,update_interval,max_updates_within_interval,dns_cache_ttl));
259         return service_ods;
260     }
261     else if(protocol == "dyndns")
262     {
263         Service::Ptr service_dyndns(new DYNDNS(protocol,hostname,login,password,Log,update_interval,max_updates_within_interval,dns_cache_ttl,Proxy,ProxyPort));
264         return service_dyndns;
265     }
266     else if(protocol == "dyns")
267     {
268         Service::Ptr service_dyns(new DYNS(protocol,hostname,login,password,Log,update_interval,max_updates_within_interval,dns_cache_ttl,Proxy,ProxyPort));
269         return service_dyns;
270     }
271     else
272     {
273         Log->print_unknown_protocol(protocol);
274         Service::Ptr service;
275         return service;
276     }
277 }
278
279
280 /**
281  * Loads a service config file, invoked by load_config_from_files.
282  * @param full_filename Filename of the service config file to load.
283  * @return 0 if all is fine, -1 otherwise.
284  */
285 int Config::load_service_config_file(const string& full_filename)
286 {
287     Log->print_load_service_conf(full_filename);
288
289     ifstream service_config_file(full_filename.c_str(),ifstream::in);
290     if(service_config_file.is_open())
291     {
292         try
293         {
294             po::variables_map vm;
295             po::parsed_options parsed_service_options = po::parse_config_file(service_config_file,*this->OptDescConfService,true);
296             po::store(parsed_service_options,vm);
297             po::notify(vm);
298
299             if(vm.count("protocol") && vm.count("host") && vm.count("login") && vm.count("password"))
300             {
301                 // create the corresponding service
302                 string protocol = vm["protocol"].as<string>();
303                 string host = vm["host"].as<string>();
304                 string login = vm["login"].as<string>();
305                 string password = vm["password"].as<string>();
306
307                 protocol = ba::to_lower_copy(protocol);
308
309                 int update_interval = 0;
310                 if ( vm.count("update_interval") )
311                     update_interval = VariablesMap["update_interval"].as<int>();
312
313                 int max_updates_within_interval = 0;
314                 if ( vm.count("max_updates_within_interval") )
315                     max_updates_within_interval = VariablesMap["max_updates_within_interval"].as<int>();
316
317                 int dns_cache_ttl = 0;
318                 if ( vm.count("dns_cache_ttl") )
319                     dns_cache_ttl = VariablesMap["dns_cache_ttl"].as<int>();
320
321                 Service::Ptr service = create_service(protocol,host,login,password,update_interval,max_updates_within_interval,dns_cache_ttl);
322                 if ( service )
323                 {
324                     ServiceHolder->add_service(service);
325                     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_dns_cache_ttl() , service->get_actual_ip(), service->get_last_updates());
326                 }
327                 else
328                     return -1;
329             }
330             else if ( vm.count("protocol") || vm.count("host") || vm.count("login") || vm.count("password") )
331             {
332                 service_config_file.close();
333                 Log->print_missing_service_conf_option(full_filename);
334                 return -1;
335             }
336         }
337         catch ( po::unknown_option e )
338         {
339             // unknown option in config file detected
340             service_config_file.close();
341             Log->print_unknown_service_conf_option(full_filename,e.what());
342             return -1;
343         }
344         catch(po::multiple_occurrences e)
345         {
346             service_config_file.close();
347             Log->print_multiple_service_conf_option(full_filename,e.what());
348             return -1;
349         }
350         service_config_file.close();
351     }
352     else
353     {
354         // error opening service config file for reading
355         Log->print_error_opening_r(full_filename);
356         return -1;
357     }
358     return 0;
359 }
360
361
362 /**
363  * Loads the main config file, invoked by load_config_from_files
364  * @param full_filename The full filename of the main config file to load
365  * @return 0 if all is fine, -1 otherwise
366  */
367 int Config::load_main_config_file(const string& full_filename)
368 {
369     Log->print_load_main_conf(full_filename);
370
371     ifstream main_config_file(full_filename.c_str(),ifstream::in);
372     if(main_config_file.is_open())
373     {
374         try
375         {
376             po::parsed_options parsed_main_options = po::parse_config_file(main_config_file,*this->OptDescConfMain,true);
377             po::store(parsed_main_options,VariablesMap);
378             po::notify(VariablesMap);
379
380             if ( VariablesMap.count("daemon_mode") )
381                 DaemonMode = VariablesMap["daemon_mode"].as<bool>();
382
383             if ( VariablesMap.count("loglevel") )
384                 Loglevel = VariablesMap["loglevel"].as<int>();
385
386             if ( VariablesMap.count("syslog") )
387                 Syslog = VariablesMap["syslog"].as<bool>();
388
389             if ( VariablesMap.count("enable_ipv6") )
390                 EnableIPv6 = VariablesMap["enable_ipv6"].as<bool>();
391
392             if ( VariablesMap.count("webcheck_url") )
393                 WebcheckIpUrl = VariablesMap["webcheck_url"].as<string>();
394
395             if ( VariablesMap.count("webcheck_url_alt") )
396                 WebcheckIpUrlAlt = VariablesMap["webcheck_url_alt"].as<string>();
397
398             if ( VariablesMap.count("http_proxy") && VariablesMap.count("http_proxy_port") )
399             {
400                 Proxy = VariablesMap["http_proxy"].as<string>();
401                 ProxyPort = VariablesMap["http_proxy_port"].as<int>();
402             }
403             else if ( VariablesMap.count("http_proxy") || VariablesMap.count("http_proxy_port") )
404             {
405                 main_config_file.close();
406                 Log->print_missing_conf_proxy_option(full_filename);
407                 return -1;
408             }
409
410             if ( VariablesMap.count("external_warning_log") )
411                 ExternalWarningLog = VariablesMap["external_warning_log"].as<string>();
412
413             if ( VariablesMap.count("external_warning_level") )
414                 ExternalWarningLevel = VariablesMap["external_warning_level"].as<int>();
415
416         }
417         catch ( po::unknown_option e )      // at the moment 04-08-2009 this exception is never thrown :-(
418         {
419             // unknown option in main config file detected
420             main_config_file.close();
421             Log->print_unknown_main_conf_option(e.what());
422             return -1;
423         }
424         catch(po::multiple_occurrences e)
425         {
426             main_config_file.close();
427             Log->print_multiple_main_conf_option(full_filename,e.what());
428             return -1;
429         }
430         main_config_file.close();
431     }
432     else
433     {
434         // error opening main config file for reading
435         Log->print_error_opening_r(full_filename);
436         return -1;
437     }
438     return 0;
439 }
440
441
442 /**
443  * Loads the main and the service config file and does the needed action.
444  * @param config_path The path to the config directory.
445  * @return 0 if all is fine, -1 otherwise
446  */
447 int Config::load_config_from_files()
448 {
449     fs::path full_config_path = fs::path(ConfigPath);
450
451     fs::directory_iterator end_iter;
452     for ( fs::directory_iterator dir_itr(full_config_path) ; dir_itr != end_iter ; ++dir_itr )
453     {
454         if( fs::is_regular_file( dir_itr->status() ) )
455         {
456             string actual_file = dir_itr->path().filename();
457             boost::regex expr(".*\\.conf?");
458              // If it is the main config file do the following
459             if ( actual_file == "bpdyndnsd.conf" )
460             {
461                 // Load the main config file
462                 string full_filename = dir_itr->path().string();
463                 if ( load_main_config_file(full_filename) != 0 )
464                     return -1;
465             }
466             // If it is a service definition file *.conf, parse it and generate the corresponding service
467             else if ( boost::regex_search( actual_file,expr ) )
468             {
469                 string full_filename = dir_itr->path().string();
470                 if ( load_service_config_file(full_filename) != 0 )
471                     return -1;
472             }
473         }
474     }
475     // Config file successfully loaded
476     Log->print_conf_loaded(ConfigPath);
477     return 0;
478 }
479
480
481 /**
482  * Getter method for member OptDescCmd.
483  * @return options_description*.
484  */
485 Config::Options_descriptionPtr Config::get_opt_desc_cmd() const
486 {
487     return OptDescCmd;
488 }
489
490
491 /**
492  * Getter method for member OptDescConfMain.
493  * @return options_description*.
494  */
495 Config::Options_descriptionPtr Config::get_opt_desc_conf_main() const
496 {
497     return OptDescConfMain;
498 }
499
500
501 /**
502  * Getter method for member OptDescConfService.
503  * @return options_description*.
504  */
505 Config::Options_descriptionPtr Config::get_opt_desc_conf_service() const
506 {
507     return OptDescConfService;
508 }
509
510
511 /**
512  * Getter for member Loglevel.
513  * @return Member Loglevel.
514  */
515 int Config::get_loglevel() const
516 {
517     return Loglevel;
518 }
519
520
521 /**
522  * Getter for member DaemonMode.
523  * @return TRUE if enabled, FALSE if disabled.
524  */
525 bool Config::get_daemon_mode() const
526 {
527     return DaemonMode;
528 }
529
530
531 /**
532  * Deletes the map with the previously parsed options.
533  * This is needed in case we reload the config and don't want the old cmd options to overwrite new config file options.
534  */
535 void Config::delete_variables_map()
536 {
537     VariablesMap.clear();
538
539     po::variables_map _variables_map;
540     VariablesMap = _variables_map;
541 }
542
543
544 /**
545  * Getter for member Syslog.
546  * @return True if logging through syslog is enabled, false otherwise.
547  */
548 bool Config::get_syslog() const
549 {
550     return Syslog;
551 }
552
553
554 /**
555  * Getter for member EnableIPv6
556  * @return Wether IPv6 should be used or not.
557  */
558 bool Config::get_enable_ipv6() const
559 {
560     return EnableIPv6;
561 }
562
563
564 /**
565  * Getter for member WebcheckIpUrl
566  * @return The primary IP Webcheck URL
567  */
568 string Config::get_webcheck_ip_url() const
569 {
570     return WebcheckIpUrl;
571 }
572
573
574 /**
575  * Getter for member WebcheckIpUrlAlt
576  * @return The alternative IP Webcheck URL
577  */
578 string Config::get_webcheck_ip_url_alt() const
579 {
580     return WebcheckIpUrlAlt;
581 }
582
583
584 /**
585  * Get member Proxy
586  * @return Proxy
587  */
588 string Config::get_proxy() const
589 {
590     return Proxy;
591 }
592
593
594 /**
595  * Get member ProxyPort
596  * @return ProxyPort
597  */
598 int Config::get_proxy_port() const
599 {
600     return ProxyPort;
601 }
602
603
604 /**
605  * Get member ExternalWarningLog
606  * @return ExternalWarningLog
607  */
608 string Config::get_external_warning_log() const
609 {
610     return ExternalWarningLog;
611 }
612
613
614 /**
615  * Get member ExternalWarningLevel
616  * @return ExternalWaringLevel
617  */
618 int Config::get_external_warning_level() const
619 {
620     return ExternalWarningLevel;
621 }
622