Logging through syslog or std(out/err) is now possible.
[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 namespace po = boost::program_options;
13 namespace fs = boost::filesystem;
14
15 using namespace std;
16
17 /**
18  * Default Constructor. Available command line and config file options with their default values are defined here.
19  */
20 Config::Config(LoggerPtr _log)
21     : Daemon_mode(false)
22     , Loglevel(0)
23     , Syslog(false)
24     , Config_path("/etc/bpdyndnsd")
25 {
26     // initialize Logger
27     Log = _log;
28
29     // Available service description config options
30     po::options_description opt_desc_service("Service description options");
31     opt_desc_service.add_options()
32         ("protocol",po::value<string>(),"The service protocol.")
33         ("host",po::value<string>(),"The hostname to update.")
34         ("login",po::value<string>(),"Login name.")
35         ("password",po::value<string>(),"Corresponding password.")
36     ;
37
38     // Available command line only options
39     po::options_description opt_desc_cmd_only("Command line only options");
40     opt_desc_cmd_only.add_options()
41         ("help,?","Show help.")
42         ("version,v","Show version.")
43         ("config,c",po::value<string>()->default_value("/etc/bpdyndnsd"),"Set the config path.")
44     ;
45
46      // Available generic options. Valid on cmd or in config file.
47     po::options_description opt_desc_generic("Generic config options");
48     opt_desc_generic.add_options()
49         ("daemon_mode",po::value<bool>()->default_value(false),"Run as system daemon.")
50         ("loglevel",po::value<int>()->default_value(0),"Loglevel.")
51         ("syslog",po::value<bool>()->default_value(false),"Use syslog facility.")
52     ;
53
54     // Define valid command line parameters
55     Opt_desc_cmd = new po::options_description("Command line options");
56     Opt_desc_cmd->add(opt_desc_cmd_only);
57     Opt_desc_cmd->add(opt_desc_generic);
58     Opt_desc_cmd->add(opt_desc_service);
59
60     // Define valid config file options
61     Opt_desc_conf_main = new po::options_description("Config file options");
62     Opt_desc_conf_main->add(opt_desc_generic);
63
64     // Define valid service file options
65     Opt_desc_conf_service = new po::options_description("Service file options");
66     Opt_desc_conf_service->add(opt_desc_service);
67
68     Log->print_constructor_call("Config");
69 }
70
71
72 /**
73  * Default Destructor
74  */
75 Config::~Config()
76 {
77     delete Opt_desc_cmd;
78     delete Opt_desc_conf_main;
79     delete Opt_desc_conf_service;
80
81     Log->print_destructor_call("Config");
82 }
83
84
85 /**
86  * Parses the command line arguments and does the needed actions.
87  * @param argc Command line argument number given to main.
88  * @param argv[] Pointer to command line argument array given to main.
89  * @return 0 if all is fine, 1 if not.
90  */
91 int Config::parse_cmd_line(int argc, char *argv[])
92 {
93     try
94     {
95         po::store(po::parse_command_line(argc, argv, *this->Opt_desc_cmd), Variables_map);
96         po::notify(Variables_map);
97
98         if ( Variables_map.count("help") )
99         {
100             Log->print_usage(Opt_desc_cmd);
101             return 1;
102         }
103         else if ( Variables_map.count("version") )
104         {
105             Log->print_version();
106             return 1;
107         }
108
109         // Create a service object if all needed options are set on the command line
110         if ( Variables_map.count("protocol") && Variables_map.count("host") && Variables_map.count("login") && Variables_map.count("password") )
111         {
112             // Get the cmd parameter values for protocol host login and password
113             string protocol = Variables_map["protocol"].as<string>();
114             string host = Variables_map["host"].as<string>();
115             string login = Variables_map["login"].as<string>();
116             string password = Variables_map["password"].as<string>();
117
118             //TODO: convert protocol option to lowercase
119
120             ServicePtr service = create_service(protocol,host,login,password);
121             if ( service )
122                 Services.push_back(service);
123             else
124                 return 1;
125         }
126         else if ( Variables_map.count("protocol") || Variables_map.count("host") || Variables_map.count("login") || Variables_map.count("password") )
127         {
128             Log->print_missing_cmd_service_option();
129             Log->print_usage(Opt_desc_cmd);
130             return 1;
131         }
132
133         if ( Variables_map.count("config") )
134         {
135             fs::path full_config_path = fs::system_complete(fs::path(Variables_map["config"].as<string>()));
136             Config_path = full_config_path.string();
137             if ( !fs::exists(full_config_path) ||  !fs::is_directory(full_config_path) )
138             {
139                 // Config path doesn't exist or is not a directory
140                 Log->print_error_config_path(Config_path);
141                 return 1;
142             }
143         }
144
145         if ( Variables_map.count("loglevel") )
146             Loglevel = Variables_map["loglevel"].as<int>();
147
148         if ( Variables_map.count("syslog") )
149             Syslog = Variables_map["syslog"].as<bool>();
150
151     }
152     catch(po::unknown_option e)
153     {
154         Log->print_unknown_cmd_option(e.what());
155         Log->print_usage(Opt_desc_cmd);
156         return 1;
157     }
158     return 0;
159 }
160
161
162 /**
163  * 
164  * @param protocol 
165  * @param host 
166  * @param login 
167  * @param password 
168  * @return A pointer to the created Service object.
169  */
170 ServicePtr Config::create_service(const string &protocol,const string &host, const string &login, const string &password)
171 {
172     if(protocol == "dhs")
173     {
174         ServicePtr service_dhs(new DHS(Log,host,login,password));
175         return service_dhs;
176     }
177     else if(protocol == "ods")
178     {
179         ServicePtr service_ods(new ODS(Log,host,login,password));
180         return service_ods;
181     }
182     else
183     {
184         Log->print_unknown_protocol(protocol);
185         ServicePtr service;
186         return service;
187     }
188 }
189
190
191 /**
192  * Loads a service config file, invoked by load_config_from_files.
193  * @param full_filename Filename of the service config file to load.
194  * @return 0 if all is fine, 3 if an unknown option was detected, 4 if the service file could not be opened for reading.
195  */
196 int Config::load_service_config_file(const string& full_filename)
197 {
198     Log->print_load_service_conf(full_filename);
199
200     ifstream service_config_file(full_filename.c_str(),ifstream::in);
201     if(service_config_file.is_open())
202     {
203         try
204         {
205             po::variables_map vm;
206             po::parsed_options parsed_service_options = po::parse_config_file(service_config_file,*this->Opt_desc_conf_service,true);
207             po::store(parsed_service_options,vm);
208             po::notify(vm);
209
210             if(vm.count("protocol") && vm.count("host") && vm.count("login") && vm.count("password"))
211             {
212                 // create the corresponding service
213                 string protocol = vm["protocol"].as<string>();
214                 string host = vm["host"].as<string>();
215                 string login = vm["login"].as<string>();
216                 string password = vm["password"].as<string>();
217
218                 // TODO: convert protocol to lowercase
219                 //protocol = tolower(protocol.c_str());
220
221                 ServicePtr service = create_service(protocol,host,login,password);
222                 if ( service )
223                 {
224                     Services.push_back(service);
225                 }
226             }
227         }
228         catch ( po::unknown_option e )
229         {
230             // unknown option in config file detected
231             service_config_file.close();
232             Log->print_unknown_service_conf_option(e.what());
233             return 1;
234         }
235         service_config_file.close();
236     }
237     else
238     {
239         // error opening service config file for reading
240         Log->print_error_opening(full_filename);
241         return 1;
242     }
243     return 0;
244 }
245
246
247 /**
248  * Loads the main config file, invoked by load_config_from_files
249  * @param full_filename The full filename of the main config file to load
250  * @return 0 if all is fine. 3 if unknown option was detected, 4 if main config file could not be opened for reading
251  */
252 int Config::load_main_config_file(const string& full_filename)
253 {
254     Log->print_load_main_conf(full_filename);
255
256     ifstream main_config_file(full_filename.c_str(),ifstream::in);
257     if(main_config_file.is_open())
258     {
259         try
260         {
261             po::parsed_options parsed_main_options = po::parse_config_file(main_config_file,*this->Opt_desc_conf_main,true);
262             po::store(parsed_main_options,Variables_map);
263             po::notify(Variables_map);
264
265             if(Variables_map.count("daemon_mode") && Variables_map.count("loglevel") && Variables_map.count("syslog"))
266             {
267                 Daemon_mode = Variables_map["daemon_mode"].as<bool>();
268                 Loglevel = Variables_map["loglevel"].as<int>();
269                 Syslog = Variables_map["syslog"].as<bool>();
270             }
271         }
272         catch ( po::unknown_option e )      // at the moment 04-08-2009 this exception is never thrown :-(
273         {
274             // unknown option in main config file detected
275             main_config_file.close();
276             Log->print_unknown_main_conf_option(e.what());
277             return 1;
278         }
279         main_config_file.close();
280     }
281     else
282     {
283         // error opening main config file for reading
284         Log->print_error_opening(full_filename);
285         return 1;
286     }
287     return 0;
288 }
289
290
291 /**
292  * Loads the main and the service config file and does the needed action.
293  * @param config_path The path to the config directory.
294  * @return 0 if all is fine.
295  */
296 int Config::load_config_from_files()
297 {
298     fs::path full_config_path = fs::path(Config_path);
299
300     fs::directory_iterator end_iter;
301     for ( fs::directory_iterator dir_itr(full_config_path) ; dir_itr != end_iter ; ++dir_itr )
302     {
303         if( fs::is_regular_file( dir_itr->status() ) )
304         {
305             string actual_file = dir_itr->path().filename();
306             boost::regex expr(".*\\.conf?");
307              // If it is the main config file do the following
308             if ( actual_file == "bpdyndnsd.conf" )
309             {
310                 // Load the main config file
311                 string full_filename = dir_itr->path().string();
312                 if ( load_main_config_file(full_filename) != 0 )
313                     return 1;
314             }
315             // If it is a service definition file *.conf, parse it and generate the corresponding service
316             else if ( boost::regex_search( actual_file,expr ) )
317             {
318                 string full_filename = dir_itr->path().string();
319                 if ( load_service_config_file(full_filename) != 0 )
320                     return 1;
321             }
322         }
323     }
324     // Config file successfully loaded
325     Log->print_conf_loaded(Config_path);
326     return 0;
327 }
328
329
330 /**
331  * Getter method for Service list member.
332  * @return Pointer to a list of Service's.
333  */
334 list<ServicePtr> Config::get_services()
335 {
336     return this->Services;
337 }
338
339
340 /**
341  * Getter method for member Opt_desc_cmd.
342  * @return options_description*.
343  */
344 po::options_description* Config::get_opt_desc_cmd()
345 {
346     return Opt_desc_cmd;
347 }
348
349
350 /**
351  * Getter method for member Opt_desc_conf_main.
352  * @return options_description*.
353  */
354 po::options_description* Config::get_opt_desc_conf_main()
355 {
356     return Opt_desc_conf_main;
357 }
358
359
360 /**
361  * Getter method for member Opt_desc_conf_service.
362  * @return options_description*.
363  */
364 po::options_description* Config::get_opt_desc_conf_service()
365 {
366     return Opt_desc_conf_service;
367 }
368
369
370 /**
371  * Getter for member Loglevel.
372  * @return Member Loglevel.
373  */
374 int Config::get_loglevel()
375 {
376     return Loglevel;
377 }
378
379
380 /**
381  * Getter for member Daemon_mode.
382  * @return TRUE if enabled, FALSE if disabled.
383  */
384 bool Config::get_daemon_mode()
385 {
386     return Daemon_mode;
387 }
388
389
390 /**
391  * Resets all shared Service pointers and clears the Services list.
392  */
393 void Config::delete_services()
394 {
395     BOOST_FOREACH( ServicePtr service, Services )
396     {
397         service.reset();
398     }
399     Services.clear();
400 }
401
402
403 /**
404  * Deletes the map with the previously parsed options.
405  * This is needed in case we reload the config and don't want the old cmd options to overwrite new config file options.
406  */
407 void Config::delete_variables_map()
408 {
409     Variables_map.clear();
410
411     po::variables_map _variables_map;
412     Variables_map = _variables_map;
413 }
414
415
416 /**
417  * Getter for member Syslog.
418  * @return True if logging through syslog is enabled, false otherwise.
419  */
420 bool Config::get_syslog()
421 {
422     return Syslog;
423 }