| 1 | /** @file |
| 2 | * @brief GNUDIP Service class implementation. This class represents the GNUDIP service. |
| 3 | * |
| 4 | * |
| 5 | * |
| 6 | * @copyright Intra2net AG |
| 7 | * @license GPLv2 |
| 8 | */ |
| 9 | |
| 10 | #include "service_gnudip.hpp" |
| 11 | #include "util.hpp" |
| 12 | |
| 13 | #include <time.h> |
| 14 | #include <boost/foreach.hpp> |
| 15 | #include <boost/regex.hpp> |
| 16 | |
| 17 | using namespace std; |
| 18 | |
| 19 | |
| 20 | /** |
| 21 | * Default Constructor, needed for object serialization. |
| 22 | */ |
| 23 | ServiceGnudip::ServiceGnudip() |
| 24 | { |
| 25 | } |
| 26 | |
| 27 | |
| 28 | /** |
| 29 | * Constructor. |
| 30 | * @param _hostname The hostname to update |
| 31 | * @param _login The login name. |
| 32 | * @param _password The corresponding password. |
| 33 | */ |
| 34 | ServiceGnudip::ServiceGnudip(const string& _protocol, const string& _gnudip_server, const string& _hostname, const string& _login, const string& _password, const Logger::Ptr& _logger, const int _update_interval, const int _max_updates_within_interval, const int _max_equal_updates_in_succession, const int _dns_cache_ttl, const string& _proxy, const int _proxy_port) |
| 35 | : GnudipServer(_gnudip_server) |
| 36 | { |
| 37 | if ( _update_interval == -1 ) // If _update_interval is default po::option_desc (not specified via config) |
| 38 | set_update_interval(15); // use default protocol value |
| 39 | else |
| 40 | set_update_interval(_update_interval); |
| 41 | |
| 42 | if ( _max_updates_within_interval == -1 ) |
| 43 | set_max_updates_within_interval(3); |
| 44 | else |
| 45 | set_max_updates_within_interval(_max_updates_within_interval); |
| 46 | |
| 47 | if ( _max_equal_updates_in_succession == -1 ) |
| 48 | set_max_equal_updates_in_succession(2); |
| 49 | else |
| 50 | set_max_equal_updates_in_succession(_max_equal_updates_in_succession); |
| 51 | |
| 52 | if ( _dns_cache_ttl == -1 ) |
| 53 | set_dns_cache_ttl(60); |
| 54 | else |
| 55 | set_dns_cache_ttl(_dns_cache_ttl); |
| 56 | |
| 57 | set_protocol(_protocol); |
| 58 | set_hostname(_hostname); |
| 59 | set_login(_login); |
| 60 | set_password(_password); |
| 61 | set_logger(_logger); |
| 62 | |
| 63 | // create http helper class |
| 64 | HTTPHelp = HTTPHelper::Ptr(new HTTPHelper(_logger,_proxy,_proxy_port,_login,_password)); |
| 65 | |
| 66 | BaseUrl = assemble_base_url(_gnudip_server); |
| 67 | } |
| 68 | |
| 69 | |
| 70 | /** |
| 71 | * Default destructor |
| 72 | */ |
| 73 | ServiceGnudip::~ServiceGnudip() |
| 74 | { |
| 75 | } |
| 76 | |
| 77 | |
| 78 | /** |
| 79 | * Assemble the dyndns update url from the given fqhn |
| 80 | * @param gnudip_server The gnudip update server. |
| 81 | * @return The assembled update url without IP. |
| 82 | */ |
| 83 | string ServiceGnudip::assemble_base_url(const string& gnudip_server) const |
| 84 | { |
| 85 | string base_url; |
| 86 | |
| 87 | base_url = "http://"; |
| 88 | base_url.append(gnudip_server); |
| 89 | base_url.append("/gnudip/cgi-bin/gdipupdt.cgi"); |
| 90 | |
| 91 | return base_url; |
| 92 | } |
| 93 | |
| 94 | |
| 95 | /** |
| 96 | * Parses the data received from the initial request, which should contain salt, time and sign. |
| 97 | * @param curl_data The complete received curl data. |
| 98 | * @return A map with salt, time and sign or an empty map. |
| 99 | */ |
| 100 | map<string,string> ServiceGnudip::parse_initial_request(const string& curl_data) const |
| 101 | { |
| 102 | map<string,string> response; |
| 103 | |
| 104 | // regex for salt |
| 105 | boost::regex expr_salt("<meta name=\"salt\" content=\"([^\"]*)\""); |
| 106 | // regex for time |
| 107 | boost::regex expr_time("<meta name=\"time\" content=\"([^\"]*)\""); |
| 108 | // regex for sign |
| 109 | boost::regex expr_sign("<meta name=\"sign\" content=\"([^\"]*)\""); |
| 110 | |
| 111 | boost::smatch matches; |
| 112 | |
| 113 | // Get the salt out of received http data |
| 114 | if ( boost::regex_search(curl_data,matches,expr_salt) ) |
| 115 | { |
| 116 | response.insert(pair<string,string>("salt",matches[1].str())); /*lint !e534 */ |
| 117 | get_logger()->print_regex_match(expr_salt.str(),matches[1].str()); |
| 118 | } |
| 119 | else |
| 120 | { |
| 121 | get_logger()->print_no_regex_match(expr_salt.str(),curl_data); |
| 122 | response.clear(); |
| 123 | return response; |
| 124 | } |
| 125 | |
| 126 | // Get the time out of received http data |
| 127 | if ( boost::regex_search(curl_data,matches,expr_time) ) |
| 128 | { |
| 129 | response.insert(pair<string,string>("time",matches[1].str())); /*lint !e534 */ |
| 130 | get_logger()->print_regex_match(expr_salt.str(),matches[1].str()); |
| 131 | } |
| 132 | else |
| 133 | { |
| 134 | get_logger()->print_no_regex_match(expr_salt.str(),curl_data); |
| 135 | response.clear(); |
| 136 | return response; |
| 137 | } |
| 138 | |
| 139 | // Get the sign out of received http data |
| 140 | if ( boost::regex_search(curl_data,matches,expr_sign) ) |
| 141 | { |
| 142 | response.insert(pair<string,string>("sign",matches[1].str())); /*lint !e534 */ |
| 143 | get_logger()->print_regex_match(expr_salt.str(),matches[1].str()); |
| 144 | } |
| 145 | else |
| 146 | { |
| 147 | get_logger()->print_no_regex_match(expr_salt.str(),curl_data); |
| 148 | response.clear(); |
| 149 | return response; |
| 150 | } |
| 151 | |
| 152 | return response; |
| 153 | } |
| 154 | |
| 155 | |
| 156 | /** |
| 157 | * Parses the data received from the update request, which should contain the return code. |
| 158 | * @param curl_data The complete received curl data. |
| 159 | * @return A string containing the return code of the update request. |
| 160 | */ |
| 161 | string ServiceGnudip::parse_return_code(const string& curl_data) const |
| 162 | { |
| 163 | string return_code; |
| 164 | |
| 165 | // regex for return code |
| 166 | boost::regex expr_retc("<meta name=\"retc\" content=\"([^\"]*)\""); |
| 167 | boost::smatch matches; |
| 168 | |
| 169 | // Get the return code out of received http data |
| 170 | if ( boost::regex_search(curl_data,matches,expr_retc) ) |
| 171 | { |
| 172 | return_code = matches[1].str(); |
| 173 | get_logger()->print_regex_match(expr_retc.str(),matches[1].str()); |
| 174 | } |
| 175 | else |
| 176 | { |
| 177 | get_logger()->print_no_regex_match(expr_retc.str(),curl_data); |
| 178 | return return_code; |
| 179 | } |
| 180 | |
| 181 | return return_code; |
| 182 | } |
| 183 | |
| 184 | |
| 185 | /** |
| 186 | * Get the assembled update url. |
| 187 | * @param salt Salt from the initial request |
| 188 | * @param time Time from the initial request |
| 189 | * @param sign Sign from the initial request |
| 190 | * @param secret Computed md5 secret in HEX |
| 191 | * @param ip IP to update |
| 192 | * @return The assembled update url. |
| 193 | */ |
| 194 | string ServiceGnudip::assemble_update_url(const string& salt, const string& curr_time, const string& sign, const string& secret, const string& ip) const |
| 195 | { |
| 196 | string url = BaseUrl; |
| 197 | |
| 198 | url.append("?salt="); |
| 199 | url.append(salt); |
| 200 | url.append("&time="); |
| 201 | url.append(curr_time); |
| 202 | url.append("&sign="); |
| 203 | url.append(sign); |
| 204 | url.append("&user="); |
| 205 | url.append(get_login()); |
| 206 | url.append("&domn="); |
| 207 | string fqhn = get_hostname(); |
| 208 | string domain = fqhn.substr(fqhn.find('.')+1); |
| 209 | url.append(domain); |
| 210 | url.append("&pass="); |
| 211 | url.append(secret); |
| 212 | url.append("&reqc=0&addr="); |
| 213 | url.append(ip); |
| 214 | |
| 215 | return url; |
| 216 | } |
| 217 | |
| 218 | |
| 219 | /** |
| 220 | * Performs the Service update. |
| 221 | * @param ip IP Address to set. |
| 222 | * @return 0 if all is fine, -1 otherwise. |
| 223 | */ |
| 224 | Service::UpdateErrorCode ServiceGnudip::perform_update(const std::string& ip) |
| 225 | { |
| 226 | if ( HTTPHelp->is_initialized() ) |
| 227 | { |
| 228 | // initial request |
| 229 | long http_status_code = HTTPHelp->http_get(BaseUrl); |
| 230 | |
| 231 | get_logger()->print_http_status_code(BaseUrl,http_status_code); |
| 232 | |
| 233 | if ( http_status_code == 200 ) |
| 234 | { |
| 235 | // Get the received http data which should contain the salt, time and sign |
| 236 | string curl_data = HTTPHelp->get_curl_data(); |
| 237 | |
| 238 | // Parse salt, time and sign out of the received data |
| 239 | map<string,string> salt_time_sign = parse_initial_request(curl_data); |
| 240 | |
| 241 | if ( salt_time_sign.empty() ) |
| 242 | { |
| 243 | get_logger()->print_could_not_parse_received_data(curl_data); |
| 244 | return GenericError; |
| 245 | } |
| 246 | |
| 247 | // at this point we have salt, time and sign parsed successfully |
| 248 | string salt, sign_time, sign; |
| 249 | |
| 250 | map<string,string>::iterator iter = salt_time_sign.find("salt"); |
| 251 | if ( iter != salt_time_sign.end() ) |
| 252 | salt = iter->second; |
| 253 | |
| 254 | iter = salt_time_sign.find("time"); |
| 255 | if ( iter != salt_time_sign.end() ) |
| 256 | sign_time = iter->second; |
| 257 | |
| 258 | iter = salt_time_sign.find("sign"); |
| 259 | if ( iter != salt_time_sign.end() ) |
| 260 | sign = iter->second; |
| 261 | |
| 262 | if ( salt.empty() || sign_time.empty() || sign.empty() ) |
| 263 | get_logger()->print_could_not_get_initial_gnudip_data(); |
| 264 | |
| 265 | // compute md5 sum from users password and get the HEX representation |
| 266 | string pw_md5_hex; |
| 267 | try |
| 268 | { |
| 269 | pw_md5_hex = Util::compute_md5_digest(get_password()); |
| 270 | } |
| 271 | catch ( exception& e ) |
| 272 | { |
| 273 | get_logger()->print_exception_md5_sum(e.what()); |
| 274 | return GenericError; |
| 275 | } |
| 276 | catch ( ... ) |
| 277 | { |
| 278 | get_logger()->print_exception_md5_sum("Unknown exception"); |
| 279 | return GenericError; |
| 280 | } |
| 281 | |
| 282 | // append "." and salt and compute md5 sum and get the HEX representation |
| 283 | pw_md5_hex.append("."); |
| 284 | pw_md5_hex.append(salt); |
| 285 | |
| 286 | string secret; |
| 287 | try |
| 288 | { |
| 289 | secret = Util::compute_md5_digest(pw_md5_hex); |
| 290 | } |
| 291 | catch ( exception& e ) |
| 292 | { |
| 293 | get_logger()->print_exception_md5_sum(e.what()); |
| 294 | return GenericError; |
| 295 | } |
| 296 | catch ( ... ) |
| 297 | { |
| 298 | get_logger()->print_exception_md5_sum("Unknown exception"); |
| 299 | return GenericError; |
| 300 | } |
| 301 | |
| 302 | // Now its time to issue the second http_get operation |
| 303 | string url = this->assemble_update_url(salt, sign_time, sign, secret, ip); |
| 304 | |
| 305 | // perform the update operation |
| 306 | http_status_code = HTTPHelp->http_get(url); |
| 307 | |
| 308 | get_logger()->print_http_status_code(url,http_status_code); |
| 309 | |
| 310 | if ( http_status_code == 200 ) |
| 311 | { |
| 312 | // parse the update request return code |
| 313 | curl_data = HTTPHelp->get_curl_data(); |
| 314 | string update_return_code = parse_return_code(curl_data); |
| 315 | if ( update_return_code == "0" ) |
| 316 | { |
| 317 | return UpdateOk; |
| 318 | } |
| 319 | else if ( update_return_code == "1" ) |
| 320 | { |
| 321 | get_logger()->print_service_not_authorized(url,get_login(),get_password()); |
| 322 | return NotAuth; |
| 323 | } |
| 324 | else |
| 325 | { |
| 326 | get_logger()->print_update_failure(url,update_return_code); |
| 327 | return UpdateError; |
| 328 | } |
| 329 | } |
| 330 | else |
| 331 | { |
| 332 | // second http get operation (update) was not successful |
| 333 | get_logger()->print_update_failure(url,http_status_code); |
| 334 | } |
| 335 | } |
| 336 | else |
| 337 | { |
| 338 | // first http get operation was not successful |
| 339 | get_logger()->print_update_failure(BaseUrl,http_status_code); |
| 340 | } |
| 341 | } |
| 342 | else |
| 343 | { |
| 344 | get_logger()->print_httphelper_not_initialized(); |
| 345 | HTTPHelp->re_initialize(); |
| 346 | } |
| 347 | return GenericError; |
| 348 | } |