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