Crossfire Server, Branch 1.12
R12190
|
00001 /* 00002 * static char *rcsid_metaserver_c = 00003 * "$Id: metaserver.c 11578 2009-02-23 22:02:27Z lalo $"; 00004 */ 00005 00006 /* 00007 CrossFire, A Multiplayer game for X-windows 00008 00009 Copyright (C) 2002,2007 Mark Wedel & Crossfire Development Team 00010 Copyright (C) 1992 Frank Tore Johansen 00011 00012 This program is free software; you can redistribute it and/or modify 00013 it under the terms of the GNU General Public License as published by 00014 the Free Software Foundation; either version 2 of the License, or 00015 (at your option) any later version. 00016 00017 This program is distributed in the hope that it will be useful, 00018 but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 GNU General Public License for more details. 00021 00022 You should have received a copy of the GNU General Public License 00023 along with this program; if not, write to the Free Software 00024 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00025 00026 The authors can be reached via e-mail at crossfire-devel@real-time.com 00027 */ 00028 00035 #include <global.h> 00036 00037 #ifndef WIN32 /* ---win32 exclude unix header files */ 00038 #include <sys/types.h> 00039 #include <sys/socket.h> 00040 #include <netinet/in.h> 00041 #include <netdb.h> 00042 #include <arpa/inet.h> 00043 00044 #endif /* end win32 */ 00045 00046 #include <pthread.h> 00047 #include <metaserver2.h> 00048 #include <version.h> 00049 00050 #ifdef HAVE_CURL_CURL_H 00051 #include <curl/curl.h> 00052 #include <curl/types.h> 00053 #include <curl/easy.h> 00054 #endif 00055 00056 static int metafd = -1; 00057 00058 static struct sockaddr_in sock; 00059 00068 void metaserver_init(void) { 00069 #ifdef WIN32 /* ***win32 metaserver_init(): init win32 socket */ 00070 struct hostent *hostbn; 00071 int temp = 1; 00072 #endif 00073 00074 if (!settings.meta_on) { 00075 metafd = -1; 00076 return; 00077 } 00078 00079 if (isdigit(settings.meta_server[0])) 00080 sock.sin_addr.s_addr = inet_addr(settings.meta_server); 00081 else { 00082 struct hostent *hostbn = gethostbyname(settings.meta_server); 00083 00084 if (hostbn == NULL) { 00085 LOG(llevDebug, "metaserver_init: Unable to resolve hostname %s\n", settings.meta_server); 00086 return; 00087 } 00088 memcpy(&sock.sin_addr, hostbn->h_addr, hostbn->h_length); 00089 } 00090 #ifdef WIN32 /* ***win32 metaserver_init(): init win32 socket */ 00091 ioctlsocket(metafd, FIONBIO , &temp); 00092 #else 00093 fcntl(metafd, F_SETFL, O_NONBLOCK); 00094 #endif 00095 if ((metafd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 00096 LOG(llevDebug, "metaserver_init: Unable to create socket, err %d\n", errno); 00097 return; 00098 } 00099 sock.sin_family = AF_INET; 00100 sock.sin_port = htons(settings.meta_port); 00101 00102 /* No hostname specified, so lets try to figure one out */ 00103 if (settings.meta_host[0] == 0) { 00104 char hostname[MAX_BUF], domain[MAX_BUF]; 00105 00106 if (gethostname(hostname, MAX_BUF-1)) { 00107 LOG(llevDebug, "metaserver_init: gethostname failed - will not report hostname\n"); 00108 return; 00109 } 00110 00111 #ifdef WIN32 /* ***win32 metaserver_init(): gethostbyname! */ 00112 hostbn = gethostbyname(hostname); 00113 if (hostbn != (struct hostent *)NULL) /* quick hack */ 00114 memcpy(domain, hostbn->h_addr, hostbn->h_length); 00115 00116 if (hostbn == (struct hostent *)NULL) { 00117 #else 00118 if (getdomainname(domain, MAX_BUF-1)) { 00119 #endif /* win32 */ 00120 LOG(llevDebug, "metaserver_init: getdomainname failed - will not report hostname\n"); 00121 return; 00122 } 00123 /* Potential overrun here but unlikely to occur */ 00124 sprintf(settings.meta_host, "%s.%s", hostname, domain); 00125 } 00126 } 00127 00134 void metaserver_update(void) { 00135 char data[MAX_BUF], num_players = 0; 00136 player *pl; 00137 00138 /* We could use socket_info.nconns, but that is not quite as accurate, 00139 * as connections in the progress of being established, are listening 00140 * but don't have a player, etc. The checks below are basically the 00141 * same as for the who commands with the addition that WIZ, AFK, and BOT 00142 * players are not counted. 00143 */ 00144 for (pl = first_player; pl != NULL; pl = pl->next) { 00145 if (pl->ob->map == NULL) 00146 continue; 00147 if (pl->hidden) 00148 continue; 00149 if (QUERY_FLAG(pl->ob, FLAG_WIZ)) 00150 continue; 00151 if (QUERY_FLAG(pl->ob, FLAG_AFK)) 00152 continue; 00153 if (pl->state != ST_PLAYING && pl->state != ST_GET_PARTY_PASSWORD) 00154 continue; 00155 if (pl->socket.is_bot) 00156 continue; 00157 num_players++; 00158 } 00159 00160 /* Only do this if we have a valid connection */ 00161 if (metafd != -1) { 00162 snprintf(data, sizeof(data), "%s|%d|%s|%s|%d|%d|%ld", settings.meta_host, num_players, 00163 FULL_VERSION, 00164 settings.meta_comment, cst_tot.ibytes, cst_tot.obytes, 00165 (long)time(NULL)-cst_tot.time_start); 00166 if (sendto(metafd, data, strlen(data), 0, (struct sockaddr *)&sock, sizeof(sock)) < 0) { 00167 LOG(llevDebug, "metaserver_update: sendto failed, err = %d\n", errno); 00168 } 00169 } 00170 00171 /* Everything inside the pthread lock/unlock is related 00172 * to metaserver2 synchronization. 00173 */ 00174 pthread_mutex_lock(&ms2_info_mutex); 00175 metaserver2_updateinfo.num_players = num_players; 00176 metaserver2_updateinfo.in_bytes = cst_tot.ibytes; 00177 metaserver2_updateinfo.out_bytes = cst_tot.obytes; 00178 metaserver2_updateinfo.uptime = (long)time(NULL)-cst_tot.time_start; 00179 pthread_mutex_unlock(&ms2_info_mutex); 00180 00181 } 00182 00196 typedef struct _MetaServer2 { 00197 char *hostname; 00198 struct _MetaServer2 *next; 00199 } MetaServer2; 00200 00201 static MetaServer2 *metaserver2; 00202 00211 typedef struct _LocalMeta2Info { 00212 int notification; /* if true, do updates to metaservers */ 00213 char *hostname; /* Hostname of this server */ 00214 int portnumber; /* Portnumber of this server */ 00215 char *html_comment; /* html comment to send to metaservers */ 00216 char *text_comment; /* text comment to send to metaservers */ 00217 char *archbase; /* Different sources for arches, maps */ 00218 char *mapbase; /* and server */ 00219 char *codebase; 00220 char *flags; /* Short flags to send to metaserver */ 00221 } LocalMeta2Info; 00222 00223 static LocalMeta2Info local_info; 00224 00225 /* These two are globals, but we declare them here. */ 00226 pthread_mutex_t ms2_info_mutex; 00227 00228 MetaServer2_UpdateInfo metaserver2_updateinfo; 00229 00236 static void free_metaserver2(MetaServer2 *ms) { 00237 free(ms->hostname); 00238 free(ms); 00239 } 00240 00255 int metaserver2_init(void) { 00256 static int has_init = 0; 00257 FILE *fp; 00258 char buf[MAX_BUF], *cp; 00259 MetaServer2 *ms2, *msnext; 00260 int comp; 00261 pthread_t thread_id; 00262 00263 #ifdef HAVE_CURL_CURL_H 00264 if (!has_init) { 00265 memset(&local_info, 0, sizeof(LocalMeta2Info)); 00266 memset(&metaserver2_updateinfo, 0, sizeof(MetaServer2_UpdateInfo)); 00267 00268 local_info.portnumber = settings.csport; 00269 metaserver2 = NULL; 00270 pthread_mutex_init(&ms2_info_mutex, NULL); 00271 curl_global_init(CURL_GLOBAL_ALL); 00272 } else { 00273 local_info.notification = 0; 00274 if (local_info.hostname) 00275 FREE_AND_CLEAR(local_info.hostname); 00276 if (local_info.html_comment) 00277 FREE_AND_CLEAR(local_info.html_comment); 00278 if (local_info.text_comment) 00279 FREE_AND_CLEAR(local_info.text_comment); 00280 if (local_info.archbase) 00281 FREE_AND_CLEAR(local_info.archbase); 00282 if (local_info.mapbase) 00283 FREE_AND_CLEAR(local_info.mapbase); 00284 if (local_info.codebase) 00285 FREE_AND_CLEAR(local_info.codebase); 00286 if (local_info.flags) 00287 FREE_AND_CLEAR(local_info.flags); 00288 for (ms2 = metaserver2; ms2; ms2 = msnext) { 00289 msnext = ms2->next; 00290 free_metaserver2(ms2); 00291 } 00292 metaserver2 = NULL; 00293 } 00294 #endif 00295 00296 /* Now load up the values from the file */ 00297 snprintf(buf, sizeof(buf), "%s/metaserver2", settings.confdir); 00298 00299 if ((fp = open_and_uncompress(buf, 0, &comp)) == NULL) { 00300 LOG(llevError, "Warning: No metaserver2 file found\n"); 00301 return 0; 00302 } 00303 while (fgets(buf, MAX_BUF-1, fp) != NULL) { 00304 if (buf[0] == '#') 00305 continue; 00306 /* eliminate newline */ 00307 if ((cp = strrchr(buf, '\n')) != NULL) 00308 *cp = '\0'; 00309 00310 /* Skip over empty lines */ 00311 if (buf[0] == 0) 00312 continue; 00313 00314 /* Find variable pairs */ 00315 00316 if ((cp = strpbrk(buf, " \t")) != NULL) { 00317 while (isspace(*cp)) 00318 *cp++ = 0; 00319 } else { 00320 /* This makes it so we don't have to do NULL checks against 00321 * cp everyplace 00322 */ 00323 cp = ""; 00324 } 00325 00326 if (!strcasecmp(buf, "metaserver2_notification")) { 00327 if (!strcasecmp(cp, "on") || !strcasecmp(cp, "true")) { 00328 local_info.notification = TRUE; 00329 } else if (!strcasecmp(cp, "off") || !strcasecmp(cp, "false")) { 00330 local_info.notification = FALSE; 00331 } else { 00332 LOG(llevError, "metaserver2: Unknown value for metaserver2_notification: %s\n", cp); 00333 } 00334 } else if (!strcasecmp(buf, "metaserver2_server")) { 00335 if (*cp != 0) { 00336 ms2 = calloc(1, sizeof(MetaServer2)); 00337 ms2->hostname = strdup(cp); 00338 ms2->next = metaserver2; 00339 metaserver2 = ms2; 00340 } else { 00341 LOG(llevError, "metaserver2: metaserver2_server must have a value.\n"); 00342 } 00343 } else if (!strcasecmp(buf, "localhostname")) { 00344 if (*cp != 0) { 00345 local_info.hostname = strdup_local(cp); 00346 } else { 00347 LOG(llevError, "metaserver2: localhostname must have a value.\n"); 00348 } 00349 } else if (!strcasecmp(buf, "portnumber")) { 00350 if (*cp != 0) { 00351 local_info.portnumber = atoi(cp); 00352 } else { 00353 LOG(llevError, "metaserver2: portnumber must have a value.\n"); 00354 } 00355 /* For the following values, it is easier to make sure 00356 * the pointers are set to something, even if it is a blank 00357 * string, so don't care if there is data in the string or not. 00358 */ 00359 } else if (!strcasecmp(buf, "html_comment")) { 00360 local_info.html_comment = strdup(cp); 00361 } else if (!strcasecmp(buf, "text_comment")) { 00362 local_info.text_comment = strdup(cp); 00363 } else if (!strcasecmp(buf, "archbase")) { 00364 local_info.archbase = strdup(cp); 00365 } else if (!strcasecmp(buf, "mapbase")) { 00366 local_info.mapbase = strdup(cp); 00367 } else if (!strcasecmp(buf, "codebase")) { 00368 local_info.codebase = strdup(cp); 00369 } else if (!strcasecmp(buf, "flags")) { 00370 local_info.flags = strdup(cp); 00371 } else { 00372 LOG(llevError, "Unknown value in metaserver2 file: %s\n", buf); 00373 } 00374 } 00375 close_and_delete(fp, comp); 00376 00377 /* If no hostname is set, can't do updates */ 00378 if (!local_info.hostname) 00379 local_info.notification = 0; 00380 00381 #ifndef HAVE_CURL_CURL_H 00382 if (local_info.notification) { 00383 LOG(llevError, "metaserver2 file is set to do notification, but libcurl is not found.\n"); 00384 LOG(llevError, "Either fix your compilation, or turn of metaserver2 notification in \n"); 00385 LOG(llevError, "the %s/metaserver2 file.\n", settings.confdir); 00386 LOG(llevError, "Exiting program.\n"); 00387 exit(1); 00388 } 00389 #endif 00390 00391 if (local_info.notification) { 00392 /* As noted above, it is much easier for the rest of the code 00393 * to not have to check for null pointers. So we do that 00394 * here, and anything that is null, we just allocate 00395 * an empty string. 00396 */ 00397 if (!local_info.html_comment) 00398 local_info.html_comment = strdup(""); 00399 if (!local_info.text_comment) 00400 local_info.text_comment = strdup(""); 00401 if (!local_info.archbase) 00402 local_info.archbase = strdup(""); 00403 if (!local_info.mapbase) 00404 local_info.mapbase = strdup(""); 00405 if (!local_info.codebase) 00406 local_info.codebase = strdup(""); 00407 if (!local_info.flags) 00408 local_info.flags = strdup(""); 00409 00410 comp = pthread_create(&thread_id, NULL, metaserver2_thread, NULL); 00411 if (comp) { 00412 LOG(llevError, "metaserver2_init: return code from pthread_create() is %d\n", comp); 00413 00414 /* Effectively true - we're not going to update the metaserver */ 00415 local_info.notification = 0; 00416 } 00417 } 00418 return local_info.notification; 00419 } 00420 00427 static size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data) { 00428 size_t realsize = size*nmemb; 00429 00430 LOG(llevDebug, "metaserver2_writer- Start of text:\n%s\n", (const char*)ptr); 00431 LOG(llevDebug, "metaserver2_writer- End of text:\n"); 00432 00433 return realsize; 00434 } 00435 00436 00442 static void metaserver2_updates(void) { 00443 #ifdef HAVE_CURL_CURL_H 00444 MetaServer2 *ms2; 00445 struct curl_httppost *formpost = NULL; 00446 struct curl_httppost *lastptr = NULL; 00447 char buf[MAX_BUF]; 00448 00449 /* First, fill in the form - note that everything has to be a string, 00450 * so we convert as needed with snprintf. 00451 * The order of fields here really isn't important. 00452 * The string after CURLFORM_COPYNAME is the name of the POST variable 00453 * as the 00454 */ 00455 curl_formadd(&formpost, &lastptr, 00456 CURLFORM_COPYNAME, "hostname", 00457 CURLFORM_COPYCONTENTS, local_info.hostname, 00458 CURLFORM_END); 00459 00460 snprintf(buf, MAX_BUF-1, "%d", local_info.portnumber); 00461 curl_formadd(&formpost, &lastptr, 00462 CURLFORM_COPYNAME, "port", 00463 CURLFORM_COPYCONTENTS, buf, 00464 CURLFORM_END); 00465 00466 curl_formadd(&formpost, &lastptr, 00467 CURLFORM_COPYNAME, "html_comment", 00468 CURLFORM_COPYCONTENTS, local_info.html_comment, 00469 CURLFORM_END); 00470 00471 curl_formadd(&formpost, &lastptr, 00472 CURLFORM_COPYNAME, "text_comment", 00473 CURLFORM_COPYCONTENTS, local_info.text_comment, 00474 CURLFORM_END); 00475 00476 curl_formadd(&formpost, &lastptr, 00477 CURLFORM_COPYNAME, "archbase", 00478 CURLFORM_COPYCONTENTS, local_info.archbase, 00479 CURLFORM_END); 00480 00481 curl_formadd(&formpost, &lastptr, 00482 CURLFORM_COPYNAME, "mapbase", 00483 CURLFORM_COPYCONTENTS, local_info.mapbase, 00484 CURLFORM_END); 00485 00486 curl_formadd(&formpost, &lastptr, 00487 CURLFORM_COPYNAME, "codebase", 00488 CURLFORM_COPYCONTENTS, local_info.codebase, 00489 CURLFORM_END); 00490 00491 curl_formadd(&formpost, &lastptr, 00492 CURLFORM_COPYNAME, "flags", 00493 CURLFORM_COPYCONTENTS, local_info.flags, 00494 CURLFORM_END); 00495 00496 pthread_mutex_lock(&ms2_info_mutex); 00497 00498 snprintf(buf, MAX_BUF-1, "%d", metaserver2_updateinfo.num_players); 00499 curl_formadd(&formpost, &lastptr, 00500 CURLFORM_COPYNAME, "num_players", 00501 CURLFORM_COPYCONTENTS, buf, 00502 CURLFORM_END); 00503 00504 snprintf(buf, MAX_BUF-1, "%d", metaserver2_updateinfo.in_bytes); 00505 curl_formadd(&formpost, &lastptr, 00506 CURLFORM_COPYNAME, "in_bytes", 00507 CURLFORM_COPYCONTENTS, buf, 00508 CURLFORM_END); 00509 00510 snprintf(buf, MAX_BUF-1, "%d", metaserver2_updateinfo.out_bytes); 00511 curl_formadd(&formpost, &lastptr, 00512 CURLFORM_COPYNAME, "out_bytes", 00513 CURLFORM_COPYCONTENTS, buf, 00514 CURLFORM_END); 00515 00516 snprintf(buf, MAX_BUF-1, "%ld", metaserver2_updateinfo.uptime); 00517 curl_formadd(&formpost, &lastptr, 00518 CURLFORM_COPYNAME, "uptime", 00519 CURLFORM_COPYCONTENTS, buf, 00520 CURLFORM_END); 00521 00522 pthread_mutex_unlock(&ms2_info_mutex); 00523 00524 /* Following few fields are global variables, 00525 * but are really defines, so won't ever change. 00526 */ 00527 curl_formadd(&formpost, &lastptr, 00528 CURLFORM_COPYNAME, "version", 00529 CURLFORM_COPYCONTENTS, FULL_VERSION, 00530 CURLFORM_END); 00531 00532 snprintf(buf, MAX_BUF-1, "%d", VERSION_SC); 00533 curl_formadd(&formpost, &lastptr, 00534 CURLFORM_COPYNAME, "sc_version", 00535 CURLFORM_COPYCONTENTS, buf, 00536 CURLFORM_END); 00537 00538 snprintf(buf, MAX_BUF-1, "%d", VERSION_CS); 00539 curl_formadd(&formpost, &lastptr, 00540 CURLFORM_COPYNAME, "cs_version", 00541 CURLFORM_COPYCONTENTS, buf, 00542 CURLFORM_END); 00543 00544 for (ms2 = metaserver2; ms2; ms2 = ms2->next) { 00545 CURL *curl; 00546 CURLcode res; 00547 00548 curl = curl_easy_init(); 00549 if (curl) { 00550 /* what URL that receives this POST */ 00551 curl_easy_setopt(curl, CURLOPT_URL, ms2->hostname); 00552 curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); 00553 00554 /* Almost always, we will get HTTP data returned 00555 * to us - instead of it going to stderr, 00556 * we want to take care of it ourselves. 00557 */ 00558 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer); 00559 res = curl_easy_perform(curl); 00560 00561 if (res) 00562 fprintf(stderr, "easy_perform got error %d\n", res); 00563 00564 /* always cleanup */ 00565 curl_easy_cleanup(curl); 00566 } 00567 } 00568 /* then cleanup the formpost chain */ 00569 curl_formfree(formpost); 00570 #endif 00571 } 00572 00586 void *metaserver2_thread(void *junk) { 00587 while (1) { 00588 metaserver2_updates(); 00589 sleep(60); 00590 } 00591 }