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