| 1 | /** @file |
| 2 | * @brief The updater class implementation. This class implements the updater logic. |
| 3 | * |
| 4 | * |
| 5 | * |
| 6 | * @copyright Intra2net AG |
| 7 | * @license GPLv2 |
| 8 | */ |
| 9 | |
| 10 | #include "updater.hpp" |
| 11 | |
| 12 | #include "serviceholder.hpp" |
| 13 | |
| 14 | |
| 15 | #include <boost/foreach.hpp> |
| 16 | #include <boost/serialization/export.hpp> |
| 17 | |
| 18 | |
| 19 | using namespace std; |
| 20 | |
| 21 | |
| 22 | /// Server error threshold: When the host IP is not current, the DNS TTL expired |
| 23 | /// and we already sent an update. |
| 24 | const int ServerErrorTTLExpiredThreshold = 15 * 60; |
| 25 | |
| 26 | |
| 27 | /** |
| 28 | * Default constructor which initializes the member Conf. |
| 29 | */ |
| 30 | Updater::Updater() |
| 31 | : IPAddrHelp(new IPAddrHelper) |
| 32 | { |
| 33 | // Initialize program wide Logger, at this point we don't know where to log and with which loglevel. |
| 34 | Log = Logger::Ptr(new Logger); |
| 35 | |
| 36 | // initialize Serviceholder |
| 37 | ServiceHolder = Serviceholder::Ptr(new Serviceholder(Log)); |
| 38 | |
| 39 | // initialize Config |
| 40 | Conf = Config::Ptr(new Config(Log,ServiceHolder)); |
| 41 | } |
| 42 | |
| 43 | |
| 44 | /** |
| 45 | * Default destructor. |
| 46 | */ |
| 47 | Updater::~Updater() |
| 48 | { |
| 49 | } |
| 50 | |
| 51 | |
| 52 | |
| 53 | /** |
| 54 | * Load config and initialize helper classes. |
| 55 | * @param argc Number of arguments in array |
| 56 | * @param argv Array with cmd options |
| 57 | */ |
| 58 | int Updater::load_config(int argc, char *argv[]) |
| 59 | { |
| 60 | // load the cmd options |
| 61 | if ( init_config_from_cmd(argc,argv) != 0 ) |
| 62 | return -1; |
| 63 | |
| 64 | // load the config and service files |
| 65 | if ( init_config_from_files() != 0 ) |
| 66 | return -1; |
| 67 | |
| 68 | // init all helper classes |
| 69 | if ( init_helper_classes() != 0 ) |
| 70 | return -1; |
| 71 | |
| 72 | return 0; |
| 73 | } |
| 74 | |
| 75 | |
| 76 | /** |
| 77 | * Reloading the config. Delete all Service objects and then init new Service objects from config files. |
| 78 | */ |
| 79 | int Updater::reload_config() |
| 80 | { |
| 81 | // serialize all service objects |
| 82 | if ( ServiceHolder->serialize_services() != 0 ) |
| 83 | return -1; |
| 84 | |
| 85 | // delete all service objects |
| 86 | ServiceHolder->delete_services(); |
| 87 | |
| 88 | // delete the actual Variables_map, perhaps with old cmd options which would overwrite new config file options. |
| 89 | Conf->delete_variables_map(); |
| 90 | |
| 91 | // clear the external send messages. |
| 92 | Log->clear_external_send_messages(); |
| 93 | |
| 94 | // load only config files |
| 95 | if ( init_config_from_files() != 0 ) |
| 96 | return -1; |
| 97 | |
| 98 | // init all helper classes |
| 99 | if ( init_helper_classes() != 0 ) |
| 100 | return -1; |
| 101 | |
| 102 | return 0; |
| 103 | } |
| 104 | |
| 105 | |
| 106 | /** |
| 107 | * Parse the command line arguments and initialize corresponding options. |
| 108 | * @param argc Number command line arguments. |
| 109 | * @param argv[] Array with arguments. |
| 110 | * @return 0 if cmd options successfully parsed, 1 if usage or version. |
| 111 | */ |
| 112 | int Updater::init_config_from_cmd(int argc, char *argv[]) const |
| 113 | { |
| 114 | // Load the command line parameters |
| 115 | if( Conf->parse_cmd_line( argc, argv) != 0) |
| 116 | return -1; |
| 117 | |
| 118 | // If we have loaded the cmd options we need to init the log facility immediately in case debugging is enabled from cmd. |
| 119 | init_log_facility(); |
| 120 | |
| 121 | // successful parsed |
| 122 | Log->print_cmd_parsed(); |
| 123 | return 0; |
| 124 | } |
| 125 | |
| 126 | |
| 127 | /** |
| 128 | * Load the main config and the service definition files in config path. |
| 129 | * @return 0 if all is fine, |
| 130 | */ |
| 131 | int Updater::init_config_from_files() const |
| 132 | { |
| 133 | // Load the main and service config files in config path |
| 134 | if ( Conf->load_config_from_files() != 0 ) |
| 135 | return -1; |
| 136 | |
| 137 | // Re-init log facility, perhaps new config file options for logger are set. |
| 138 | // These config file options will only overwrite the cmd options if the SIGHUP (reload config) is caught. |
| 139 | init_log_facility(); |
| 140 | |
| 141 | // Here we are. All Service Objects should be initialized, so it is time to deserialize the old service objects and compare them. |
| 142 | if ( ServiceHolder->deserialize_services() != 0 ) |
| 143 | return -1; |
| 144 | |
| 145 | // successful loaded |
| 146 | Log->print_conf_files_parsed(); |
| 147 | return 0; |
| 148 | } |
| 149 | |
| 150 | |
| 151 | /** |
| 152 | * Init all Helper classes |
| 153 | * @return |
| 154 | */ |
| 155 | int Updater::init_helper_classes() |
| 156 | { |
| 157 | // Initialize IPHelper |
| 158 | if ( init_ip_helper() != 0 ) |
| 159 | return -1; |
| 160 | |
| 161 | return 0; |
| 162 | } |
| 163 | |
| 164 | |
| 165 | /** |
| 166 | * Init the IPHelp member with needed values. |
| 167 | * @return 0 if all is fine, -1 otherwise. |
| 168 | */ |
| 169 | int Updater::init_ip_helper() |
| 170 | { |
| 171 | // Try to get deserialized IPAddrHelper from ServiceHolder |
| 172 | IPAddrHelper::Ptr _ip_addr_help = ServiceHolder->get_ip_addr_helper(); |
| 173 | if ( _ip_addr_help.use_count() != 0 ) |
| 174 | { |
| 175 | // Initialize IPHelper |
| 176 | IPAddrHelp = IPAddrHelper::Ptr( new IPAddrHelper( Log, Conf->get_webcheck_ip_url(), Conf->get_webcheck_ip_url_alt(), Conf->get_webcheck_interval(), _ip_addr_help->get_last_webcheck(), Conf->get_enable_ipv6(), Conf->get_proxy(), Conf->get_proxy_port() ) ); |
| 177 | } |
| 178 | else |
| 179 | { |
| 180 | // IPAddrHelper from ServiceHolder was not declared, so init oen with LastWebcheck 0 |
| 181 | IPAddrHelp = IPAddrHelper::Ptr( new IPAddrHelper( Log, Conf->get_webcheck_ip_url(), Conf->get_webcheck_ip_url_alt(), Conf->get_webcheck_interval(), (size_t)0, Conf->get_enable_ipv6(), Conf->get_proxy(), Conf->get_proxy_port() ) ); |
| 182 | } |
| 183 | |
| 184 | // Put the IPAddrHelper into ServiceHolder, so the LastWebcheck state will be serialized too. |
| 185 | ServiceHolder->set_ip_addr_helper(IPAddrHelp); |
| 186 | |
| 187 | return 0; |
| 188 | } |
| 189 | |
| 190 | |
| 191 | /** |
| 192 | * Getter for member Config. |
| 193 | * @return Member Config. |
| 194 | */ |
| 195 | Config::Ptr Updater::get_config() const |
| 196 | { |
| 197 | return Conf; |
| 198 | } |
| 199 | |
| 200 | |
| 201 | /** |
| 202 | * Getter for member Logger. |
| 203 | * @return Member Logger. |
| 204 | */ |
| 205 | Logger::Ptr Updater::get_logger() const |
| 206 | { |
| 207 | return Log; |
| 208 | } |
| 209 | |
| 210 | |
| 211 | /** |
| 212 | * Initialize the logging facility with loglevel and syslog. |
| 213 | */ |
| 214 | void Updater::init_log_facility() const |
| 215 | { |
| 216 | Log->set_log_facility(Conf->get_loglevel(),Conf->get_syslog(),Conf->get_external_warning_log(),Conf->get_external_warning_level(),Conf->get_external_log_only_once()); |
| 217 | Log->print_init_log_facility(); |
| 218 | |
| 219 | Log->set_log_passwords(Conf->get_log_passwords()); |
| 220 | } |
| 221 | |
| 222 | |
| 223 | /** |
| 224 | * Update all configured services. |
| 225 | * @param changed_to_online True if we just changed to online, false if we were already online |
| 226 | */ |
| 227 | void Updater::update_services(bool changed_to_online) const |
| 228 | { |
| 229 | // Get all services from the ServiceHolder. |
| 230 | list<Service::Ptr> services = ServiceHolder->get_services(); |
| 231 | |
| 232 | // Get the actual IP of this host. |
| 233 | string ip_host = IPAddrHelp->get_actual_ip(Conf->get_webcheck_enabled(), changed_to_online, Conf->get_wan_ip_override()); |
| 234 | if ( ip_host.empty() ) |
| 235 | { |
| 236 | Log->print_no_wan_ip(changed_to_online); |
| 237 | return; |
| 238 | } else |
| 239 | { |
| 240 | Log->print_external_wan_ip(changed_to_online, ip_host); |
| 241 | } |
| 242 | |
| 243 | BOOST_FOREACH(Service::Ptr &service, services ) |
| 244 | { |
| 245 | string ip_last_update = service->get_actual_ip(); |
| 246 | string hostname = service->get_hostname(); |
| 247 | time_t current_time = time(NULL); |
| 248 | |
| 249 | // Get the last update time of the service. |
| 250 | time_t lastupdated = service->get_last_update_time(); |
| 251 | |
| 252 | Log->print_check_service_update(hostname, current_time, lastupdated); |
| 253 | |
| 254 | // Do a DNS Query for the dynamic hostname. |
| 255 | string ip_dns_recheck = IPAddrHelp->dns_query(hostname); |
| 256 | |
| 257 | Log->print_cached_dns_entry(hostname, ip_dns_recheck, ip_last_update, ip_host); |
| 258 | |
| 259 | // Test if the DNS-Record could not be found. |
| 260 | // If DNS-Record was not found but service was not activated, the hostname could be deactivated, offline or not existent. In this case try a ordinary update. |
| 261 | if ( ip_dns_recheck.empty() && service->get_activated() ) |
| 262 | { |
| 263 | Log->print_dns_lookup_failed(changed_to_online, hostname); |
| 264 | continue; |
| 265 | } |
| 266 | |
| 267 | // Test if the actual DNS-Record differs from the host's IP. |
| 268 | if (ip_host == ip_dns_recheck ) |
| 269 | { |
| 270 | Log->print_no_update_needed(changed_to_online, hostname, ip_dns_recheck, ip_last_update, ip_host, lastupdated); |
| 271 | // No update needed |
| 272 | continue; |
| 273 | } |
| 274 | |
| 275 | // Check if the IP set in last update differs from the actual host's ip. |
| 276 | if ( ip_last_update != ip_host ) |
| 277 | { |
| 278 | // Update |
| 279 | if ( !ip_dns_recheck.empty() ) |
| 280 | { |
| 281 | Log->print_update_service(hostname, ip_dns_recheck, ip_last_update, ip_host, lastupdated); |
| 282 | } |
| 283 | else |
| 284 | { |
| 285 | // Service gets updated the first time and no DNS-Record was found. This is either a initial update or the hostname is really not available. |
| 286 | Log->print_update_service(hostname, "<NO IP SET>", ip_last_update, ip_host, lastupdated); |
| 287 | } |
| 288 | |
| 289 | service->update(ip_host, current_time, changed_to_online); |
| 290 | } |
| 291 | else |
| 292 | { |
| 293 | int dns_cache_ttl = service->get_dns_cache_ttl(); |
| 294 | |
| 295 | // Add server error threshold so we don't jam the server with updates for the same IP |
| 296 | // in case the server doesn't hand out the already sent IP via DNS |
| 297 | // (might indicate a server error). |
| 298 | dns_cache_ttl += ServerErrorTTLExpiredThreshold; |
| 299 | |
| 300 | // Check if DNS Cache TTL is expired, if so, then update the same IP again. |
| 301 | if ( (lastupdated + dns_cache_ttl) < current_time ) |
| 302 | { |
| 303 | // Update |
| 304 | Log->print_update_service_ttl_expired(hostname, ip_dns_recheck, ip_last_update, ip_host, lastupdated, dns_cache_ttl, current_time); |
| 305 | service->update(ip_host, current_time, changed_to_online); |
| 306 | } |
| 307 | else |
| 308 | { |
| 309 | // DNS cache TTL isn't expired |
| 310 | Log->print_update_service_ttl_not_expired(changed_to_online, hostname, ip_dns_recheck, ip_last_update, ip_host, lastupdated, dns_cache_ttl, current_time); |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | |
| 317 | /** |
| 318 | * Getter for member ServiceHolder. |
| 319 | * @return ServiceHolder |
| 320 | */ |
| 321 | Serviceholder::Ptr Updater::get_service_holder() const |
| 322 | { |
| 323 | return ServiceHolder; |
| 324 | } |