Started with boost/serialization error handling.
[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 // Following boost macros are needed for serialization of derived classes through a base class pointer (Service *).
13 BOOST_CLASS_EXPORT_GUID(ODS, "ODS")
14 BOOST_CLASS_EXPORT_GUID(DHS, "DHS")
15
16 namespace po = boost::program_options;
17 namespace fs = boost::filesystem;
18
19 using namespace std;
20
21 /**
22  * Default Constructor. Available command line and config file options with their default values are defined here.
23  */
24 Config::Config(LoggerPtr _log)
25     : Log(new Logger)
26     , Daemon_mode(false)
27     , Loglevel(0)
28     , Syslog(false)
29     , Config_path("/etc/bpdyndnsd")
30 {
31     // initialize Logger
32     Log = _log;
33
34     // Available service description config options
35     po::options_description opt_desc_service("Service description options");
36     opt_desc_service.add_options()
37         ("protocol",po::value<string>(),"The service protocol.")
38         ("host",po::value<string>(),"The hostname to update.")
39         ("login",po::value<string>(),"Login name.")
40         ("password",po::value<string>(),"Corresponding password.")
41     ;
42
43     // Available command line only options
44     po::options_description opt_desc_cmd_only("Command line only options");
45     opt_desc_cmd_only.add_options()
46         ("help,?","Show help.")
47         ("version,v","Show version.")
48         ("config,c",po::value<string>()->default_value("/etc/bpdyndnsd"),"Set the config path.")
49     ;
50
51      // Available generic options. Valid on cmd or in config file.
52     po::options_description opt_desc_generic("Generic config options");
53     opt_desc_generic.add_options()
54         ("daemon_mode",po::value<bool>()->default_value(false),"Run as system daemon.")
55         ("loglevel",po::value<int>()->default_value(0),"Loglevel.")
56         ("syslog",po::value<bool>()->default_value(false),"Use syslog facility.")
57     ;
58
59     // Define valid command line parameters
60     Options_descriptionPtr _opt_desc_cmd(new po::options_description("Command line options"));
61     Opt_desc_cmd = _opt_desc_cmd;
62     _opt_desc_cmd.reset();
63     Opt_desc_cmd->add(opt_desc_cmd_only);
64     Opt_desc_cmd->add(opt_desc_generic);
65     Opt_desc_cmd->add(opt_desc_service);
66
67     // Define valid config file options
68     Options_descriptionPtr _opt_desc_conf_main(new po::options_description("Config file options"));
69     Opt_desc_conf_main = _opt_desc_conf_main;
70     _opt_desc_conf_main.reset();
71     Opt_desc_conf_main->add(opt_desc_generic);
72
73     // Define valid service file options
74     Options_descriptionPtr _opt_desc_conf_service(new po::options_description("Service file options"));
75     Opt_desc_conf_service = _opt_desc_conf_service;
76     _opt_desc_conf_service.reset();
77     Opt_desc_conf_service->add(opt_desc_service);
78 }
79
80
81 /**
82  * Default Destructor
83  */
84 Config::~Config()
85 {
86 }
87
88
89 /**
90  * This function serializes all Service objects in Services and Old_services (where the update Timeout isn't expired) into a specified file.
91  * @return 0 if all is fine, -1 otherwise.
92  */
93 int Config::serialize_services()
94 {
95     //TODO: error handling
96
97     // First of all we have to put all Service objects in a Serviceholder object.
98
99     ServiceholderPtr service_holder(new Serviceholder());
100
101     BOOST_FOREACH(ServicePtr service, Services)
102     {
103         service_holder->add_service(service);
104     }
105
106     int current_time = time(NULL);
107
108     BOOST_FOREACH(ServicePtr service, Old_services)
109     {
110         if ( ( service->get_lastupdated() + service->get_timeout() ) >= current_time )  // Update Timeout of service isn't expired.
111             service_holder->add_service(service);
112     }
113
114     // Then we can serialize this separate Object into one file.
115
116     ofstream ofs(OBJECT_FILE);
117     if ( ofs.is_open() )
118     {
119         Serviceholder* _service_holder = service_holder.get();
120         try
121         {
122             boost::archive::text_oarchive oa(ofs);
123             oa << _service_holder;
124         }
125         catch( boost::archive_exception e )
126         {
127             Log->print_exception_serialize(e.what());
128             ofs.close();
129             return -1;
130         }
131
132         ofs.close();
133     }
134
135     Log->print_serialized_objects_success();
136
137     return 0;
138 }
139
140
141 int Config::deserialize_services()
142 {
143     // TODO: error handling
144     // TODO: redefine algorithm, to avoid tripple FOREACHes ;-)
145
146     ifstream ifs(OBJECT_FILE);
147     if ( ifs.is_open() )
148     {
149         Serviceholder* _service_holder;
150         boost::archive::text_iarchive ia(ifs);
151         ia >> _service_holder;       // ends up in default constructor call for Serviceholder
152         ServiceholderPtr service_holder(_service_holder);
153         ifs.close();
154
155         list<ServicePtr> _old_services = service_holder->get_hold_services();
156
157         BOOST_FOREACH(ServicePtr old_service, _old_services)
158         {
159             // Put the deserialized service into the Old_service member list.
160             Old_services.push_back(old_service);
161             Log->print_service_object("Deserialized following Service object:", old_service->get_protocol(), old_service->get_hostname(), old_service->get_login() ,old_service->get_password() ,old_service->get_actual_ip(), old_service->get_lastupdated());
162         }
163
164         BOOST_FOREACH(ServicePtr service, Services)
165         {
166             BOOST_FOREACH(ServicePtr old_service, Old_services)
167             {
168                 if ( *service == *old_service )
169                 {
170                     service->set_lastupdated(old_service->get_lastupdated());
171                     service->set_actual_ip(old_service->get_actual_ip());
172                     Log->print_service_object("New Service object with adopted values:", service->get_protocol(), service->get_hostname(), service->get_login() ,service->get_password() ,service->get_actual_ip(), service->get_lastupdated());
173                     // We have adopted the values of the old_service. Just set lastupdated and timeout to 0, so this old_service wont be serialized.
174                     old_service->set_lastupdated(0);
175                     old_service->set_timeout(0);
176                 }
177             }
178         }
179         //TODO: debug output of Old_services (is element really removed from list if values were adopted???)
180         Log->print_deserialized_objects_success();
181     }
182     else
183     {
184         Log->print_error_opening(OBJECT_FILE);
185     }
186
187     return 0;
188 }
189
190
191 /**
192  * Parses the command line arguments and does the needed actions.
193  * @param argc Command line argument number given to main.
194  * @param argv[] Pointer to command line argument array given to main.
195  * @return 0 if all is fine, 1 if not.
196  */
197 int Config::parse_cmd_line(int argc, char *argv[])
198 {
199     try
200     {
201         po::store(po::parse_command_line(argc, argv, *this->Opt_desc_cmd), Variables_map);
202         po::notify(Variables_map);
203
204         if ( Variables_map.count("help") )
205         {
206             Log->print_usage(Opt_desc_cmd);
207             return 1;
208         }
209         else if ( Variables_map.count("version") )
210         {
211             Log->print_version();
212             return 1;
213         }
214
215         // Create a service object if all needed options are set on the command line
216         if ( Variables_map.count("protocol") && Variables_map.count("host") && Variables_map.count("login") && Variables_map.count("password") )
217         {
218             // Get the cmd parameter values for protocol host login and password
219             string protocol = Variables_map["protocol"].as<string>();
220             string host = Variables_map["host"].as<string>();
221             string login = Variables_map["login"].as<string>();
222             string password = Variables_map["password"].as<string>();
223
224             //TODO: convert protocol option to lowercase
225
226             ServicePtr service = create_service(protocol,host,login,password);
227             if ( service )
228                 Services.push_back(service);
229             else
230                 return 1;
231         }
232         else if ( Variables_map.count("protocol") || Variables_map.count("host") || Variables_map.count("login") || Variables_map.count("password") )
233         {
234             Log->print_missing_cmd_service_option();
235             Log->print_usage(Opt_desc_cmd);
236             return 1;
237         }
238
239         if ( Variables_map.count("config") )
240         {
241             fs::path full_config_path = fs::system_complete(fs::path(Variables_map["config"].as<string>()));
242             Config_path = full_config_path.string();
243             if ( !fs::exists(full_config_path) ||  !fs::is_directory(full_config_path) )
244             {
245                 // Config path doesn't exist or is not a directory
246                 Log->print_error_config_path(Config_path);
247                 return 1;
248             }
249         }
250
251         if ( Variables_map.count("loglevel") )
252             Loglevel = Variables_map["loglevel"].as<int>();
253
254         if ( Variables_map.count("syslog") )
255             Syslog = Variables_map["syslog"].as<bool>();
256
257     }
258     catch(po::unknown_option e)
259     {
260         Log->print_unknown_cmd_option(e.what());
261         Log->print_usage(Opt_desc_cmd);
262         return 1;
263     }
264     return 0;
265 }
266
267
268 /**
269  * 
270  * @param protocol 
271  * @param host 
272  * @param login 
273  * @param password 
274  * @return A pointer to the created Service object.
275  */
276 ServicePtr Config::create_service(const string &protocol,const string &hostname, const string &login, const string &password)
277 {
278     if(protocol == "dhs")
279     {
280         ServicePtr service_dhs(new DHS(protocol,hostname,login,password,Log,0,60,1));
281         return service_dhs;
282     }
283     else if(protocol == "ods")
284     {
285         ServicePtr service_ods(new ODS(protocol,hostname,login,password,Log,0,60,1));
286         return service_ods;
287     }
288     else
289     {
290         Log->print_unknown_protocol(protocol);
291         ServicePtr service;
292         return service;
293     }
294 }
295
296
297 /**
298  * Loads a service config file, invoked by load_config_from_files.
299  * @param full_filename Filename of the service config file to load.
300  * @return 0 if all is fine, 3 if an unknown option was detected, 4 if the service file could not be opened for reading.
301  */
302 int Config::load_service_config_file(const string& full_filename)
303 {
304     Log->print_load_service_conf(full_filename);
305
306     ifstream service_config_file(full_filename.c_str(),ifstream::in);
307     if(service_config_file.is_open())
308     {
309         try
310         {
311             po::variables_map vm;
312             po::parsed_options parsed_service_options = po::parse_config_file(service_config_file,*this->Opt_desc_conf_service,true);
313             po::store(parsed_service_options,vm);
314             po::notify(vm);
315
316             if(vm.count("protocol") && vm.count("host") && vm.count("login") && vm.count("password"))
317             {
318                 // create the corresponding service
319                 string protocol = vm["protocol"].as<string>();
320                 string host = vm["host"].as<string>();
321                 string login = vm["login"].as<string>();
322                 string password = vm["password"].as<string>();
323
324                 // TODO: convert protocol to lowercase
325                 //protocol = tolower(protocol.c_str());
326
327                 ServicePtr service = create_service(protocol,host,login,password);
328                 if ( service )
329                 {
330                     Services.push_back(service);
331                     Log->print_service_object("New Service object from config:", service->get_protocol(), service->get_hostname(), service->get_login() ,service->get_password() ,service->get_actual_ip(), service->get_lastupdated());
332                 }
333             }
334         }
335         catch ( po::unknown_option e )
336         {
337             // unknown option in config file detected
338             service_config_file.close();
339             Log->print_unknown_service_conf_option(e.what());
340             return 1;
341         }
342         service_config_file.close();
343     }
344     else
345     {
346         // error opening service config file for reading
347         Log->print_error_opening(full_filename);
348         return 1;
349     }
350     return 0;
351 }
352
353
354 /**
355  * Loads the main config file, invoked by load_config_from_files
356  * @param full_filename The full filename of the main config file to load
357  * @return 0 if all is fine. 3 if unknown option was detected, 4 if main config file could not be opened for reading
358  */
359 int Config::load_main_config_file(const string& full_filename)
360 {
361     Log->print_load_main_conf(full_filename);
362
363     ifstream main_config_file(full_filename.c_str(),ifstream::in);
364     if(main_config_file.is_open())
365     {
366         try
367         {
368             po::parsed_options parsed_main_options = po::parse_config_file(main_config_file,*this->Opt_desc_conf_main,true);
369             po::store(parsed_main_options,Variables_map);
370             po::notify(Variables_map);
371
372             if(Variables_map.count("daemon_mode") && Variables_map.count("loglevel") && Variables_map.count("syslog"))
373             {
374                 Daemon_mode = Variables_map["daemon_mode"].as<bool>();
375                 Loglevel = Variables_map["loglevel"].as<int>();
376                 Syslog = Variables_map["syslog"].as<bool>();
377             }
378         }
379         catch ( po::unknown_option e )      // at the moment 04-08-2009 this exception is never thrown :-(
380         {
381             // unknown option in main config file detected
382             main_config_file.close();
383             Log->print_unknown_main_conf_option(e.what());
384             return 1;
385         }
386         main_config_file.close();
387     }
388     else
389     {
390         // error opening main config file for reading
391         Log->print_error_opening(full_filename);
392         return 1;
393     }
394     return 0;
395 }
396
397
398 /**
399  * Loads the main and the service config file and does the needed action.
400  * @param config_path The path to the config directory.
401  * @return 0 if all is fine.
402  */
403 int Config::load_config_from_files()
404 {
405     fs::path full_config_path = fs::path(Config_path);
406
407     fs::directory_iterator end_iter;
408     for ( fs::directory_iterator dir_itr(full_config_path) ; dir_itr != end_iter ; ++dir_itr )
409     {
410         if( fs::is_regular_file( dir_itr->status() ) )
411         {
412             string actual_file = dir_itr->path().filename();
413             boost::regex expr(".*\\.conf?");
414              // If it is the main config file do the following
415             if ( actual_file == "bpdyndnsd.conf" )
416             {
417                 // Load the main config file
418                 string full_filename = dir_itr->path().string();
419                 if ( load_main_config_file(full_filename) != 0 )
420                     return 1;
421             }
422             // If it is a service definition file *.conf, parse it and generate the corresponding service
423             else if ( boost::regex_search( actual_file,expr ) )
424             {
425                 string full_filename = dir_itr->path().string();
426                 if ( load_service_config_file(full_filename) != 0 )
427                     return 1;
428             }
429         }
430     }
431     // Config file successfully loaded
432     Log->print_conf_loaded(Config_path);
433     return 0;
434 }
435
436
437 /**
438  * Getter method for Service list member.
439  * @return Pointer to a list of Service's.
440  */
441 list<ServicePtr> Config::get_services()
442 {
443     return this->Services;
444 }
445
446
447 /**
448  * Getter method for member Opt_desc_cmd.
449  * @return options_description*.
450  */
451 Options_descriptionPtr Config::get_opt_desc_cmd()
452 {
453     return Opt_desc_cmd;
454 }
455
456
457 /**
458  * Getter method for member Opt_desc_conf_main.
459  * @return options_description*.
460  */
461 Options_descriptionPtr Config::get_opt_desc_conf_main()
462 {
463     return Opt_desc_conf_main;
464 }
465
466
467 /**
468  * Getter method for member Opt_desc_conf_service.
469  * @return options_description*.
470  */
471 Options_descriptionPtr Config::get_opt_desc_conf_service()
472 {
473     return Opt_desc_conf_service;
474 }
475
476
477 /**
478  * Getter for member Loglevel.
479  * @return Member Loglevel.
480  */
481 int Config::get_loglevel()
482 {
483     return Loglevel;
484 }
485
486
487 /**
488  * Getter for member Daemon_mode.
489  * @return TRUE if enabled, FALSE if disabled.
490  */
491 bool Config::get_daemon_mode()
492 {
493     return Daemon_mode;
494 }
495
496
497 /**
498  * Resets all shared Service pointers and clears the Services list.
499  */
500 void Config::delete_services()
501 {
502     BOOST_FOREACH( ServicePtr service, Services )
503     {
504         service.reset();
505     }
506     Services.clear();
507 }
508
509
510 /**
511  * Deletes the map with the previously parsed options.
512  * This is needed in case we reload the config and don't want the old cmd options to overwrite new config file options.
513  */
514 void Config::delete_variables_map()
515 {
516     Variables_map.clear();
517
518     po::variables_map _variables_map;
519     Variables_map = _variables_map;
520 }
521
522
523 /**
524  * Getter for member Syslog.
525  * @return True if logging through syslog is enabled, false otherwise.
526  */
527 bool Config::get_syslog()
528 {
529     return Syslog;
530 }