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