|
Crossfire Client, Trunk
R18666
|
00001 const char * const rcsid_common_metaserver_c = 00002 "$Id: metaserver.c 15072 2011-09-10 10:59:18Z ryo_saeba $"; 00003 /* 00004 Crossfire client, a client program for the crossfire program. 00005 00006 Copyright (C) 2001-2010 Mark Wedel & Crossfire Development Team 00007 00008 This program is free software; you can redistribute it and/or modify 00009 it under the terms of the GNU General Public License as published by 00010 the Free Software Foundation; either version 2 of the License, or 00011 (at your option) any later version. 00012 00013 This program is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00016 GNU General Public License for more details. 00017 00018 You should have received a copy of the GNU General Public License 00019 along with this program; if not, write to the Free Software 00020 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00021 00022 The author can be reached via e-mail to crossfire-devel@real-time.com 00023 */ 00024 00032 #ifndef WIN32 00033 #include <netdb.h> 00034 #include <sys/types.h> 00035 #include <sys/socket.h> 00036 #include <netinet/in.h> 00037 #include <arpa/inet.h> 00038 #endif /* WIN32 */ 00039 00040 #include <ctype.h> 00041 #include <stdio.h> 00042 #include <stdlib.h> 00043 00044 #include <client.h> 00045 #include <cconfig.h> 00046 #include <external.h> 00047 00048 #include <metaserver.h> 00049 00050 #ifdef HAVE_CURL_CURL_H 00051 #include <curl/curl.h> 00052 #include <curl/easy.h> 00053 #endif 00054 00055 Meta_Info *meta_servers = NULL; 00056 00057 int meta_numservers = 0; 00058 00059 int meta_sort(Meta_Info *m1, Meta_Info *m2) { return strcasecmp(m1->hostname, m2->hostname); } 00060 00072 int check_server_version(int entry) 00073 { 00074 00075 /* No version information - nothing to do. */ 00076 if (!meta_servers[entry].sc_version || !meta_servers[entry].cs_version) 00077 return 1; 00078 00079 if (meta_servers[entry].sc_version != VERSION_SC) { 00080 /* 1027->1028 removed a bunch of old commands, so a 1028 00081 * version client can still play on a 1027 server, so 00082 * special hard code that. 00083 * 00084 * Likewise, 1028->1029 just changed how weapon_speed 00085 * should be interperted on the client - the client 00086 * does the right thing, so not problem with a 1029 00087 * client playing on 1028 or 1027 server. 00088 * 00089 * A 1028 client could in practice play on a 1029 00090 * server, since at the protocol level, data is the same - 00091 * the client would just have screwed up weapon_sp values. 00092 */ 00093 if ((VERSION_SC == 1028 || VERSION_SC==1029) && 00094 (meta_servers[entry].sc_version==1027 || 00095 meta_servers[entry].sc_version==1028)) 00096 return 1; 00097 } 00098 if (meta_servers[entry].cs_version != VERSION_CS) return 0; 00099 00100 return 1; 00101 } 00102 00103 /***************************************************************************** 00104 * Start of cache related functions. 00105 *****************************************************************************/ 00106 int cached_servers_num = 0; 00107 char *cached_servers_name[CACHED_SERVERS_MAX]; 00108 char *cached_servers_ip[CACHED_SERVERS_MAX]; 00109 static int cached_servers_loaded = 0; 00110 const char *cached_server_file = NULL; 00111 00126 static void metaserver_load_cache(void) { 00127 char name[MS_LARGE_BUF], ip[MS_LARGE_BUF]; 00128 FILE *cache; 00129 00130 if (cached_servers_loaded || !cached_server_file) 00131 return; 00132 00133 /* If failure, we don't want to load again */ 00134 cached_servers_loaded = 1; 00135 cached_servers_num = 0; 00136 00137 cache = fopen(cached_server_file, "r"); 00138 if (!cache) 00139 return; 00140 00141 while (cached_servers_num < CACHED_SERVERS_MAX 00142 && fgets(name, MS_LARGE_BUF, cache) != NULL 00143 && fgets(ip , MS_LARGE_BUF, cache) != NULL) { 00144 ip[strlen(ip)-1] = 0; 00145 name[strlen(name)-1] = 0; 00146 cached_servers_ip[cached_servers_num] = strdup(ip); 00147 cached_servers_name[cached_servers_num++] = strdup(name); 00148 } 00149 fclose(cache); 00150 } 00151 00155 static void metaserver_save_cache(void) { 00156 FILE *cache; 00157 int server; 00158 00159 if (!cached_server_file) 00160 return; 00161 00162 cache = fopen(cached_server_file, "w"); 00163 if (!cache) 00164 return; 00165 00166 for (server = 0; server < cached_servers_num; server++) { 00167 fprintf(cache, "%s\n", cached_servers_name[server]); 00168 fprintf(cache, "%s\n", cached_servers_ip[server]); 00169 } 00170 fclose(cache); 00171 } 00172 00178 void metaserver_update_cache(const char *server_name, const char *server_ip) { 00179 int index; 00180 00181 /* 00182 * Try to find the given server name in the existing server cache. If the 00183 * zero-based index ends up equal to the one-based number of cached 00184 * servers, it was not found. 00185 */ 00186 for (index = 0; index < cached_servers_num; index++) { 00187 if (strcmp(server_name, cached_servers_name[index]) == 0) { 00188 break; 00189 } 00190 } 00191 00192 /* 00193 * If server is already first in the cache list, nothing else needs to be 00194 * done, otherwise, the server needs to be cached. 00195 */ 00196 if (index != 0 || !cached_servers_num) { 00197 char *name; 00198 char *ip; 00199 int copy; 00200 00201 if (index == cached_servers_num) { 00202 /* 00203 * If the server was not found in the cache, expand the cache size 00204 * by one unless that creates too many entries. 00205 */ 00206 name = strdup(server_name); 00207 ip = strdup(server_ip); 00208 cached_servers_num++; 00209 if (cached_servers_num > CACHED_SERVERS_MAX) { 00210 cached_servers_num--; 00211 free(cached_servers_name[cached_servers_num-1]); 00212 free(cached_servers_ip[cached_servers_num-1]); 00213 } 00214 } else { 00215 /* 00216 * If the server was already listed in the cache, grab a copy of 00217 * the prior listing. 00218 */ 00219 name = cached_servers_name[index]; 00220 ip = cached_servers_ip[index]; 00221 } 00222 00223 /* 00224 * If the server as already listed, move all the cached items above 00225 * the listing down a slot, otherwise, move the whole list down a 00226 * notch. This "empties" the top slot. 00227 */ 00228 for (copy = MIN(index, CACHED_SERVERS_MAX-1); copy > 0; copy--) { 00229 cached_servers_name[copy] = cached_servers_name[copy-1]; 00230 cached_servers_ip[copy] = cached_servers_ip[copy-1]; 00231 } 00232 00233 /* 00234 * Put the added server information at the top of the cache list, and 00235 * save the changes. 00236 */ 00237 cached_servers_name[0] = name; 00238 cached_servers_ip[0] = ip; 00239 metaserver_save_cache(); 00240 } 00241 } 00242 00243 /***************************************************************************** 00244 * End of cache related functions. 00245 *****************************************************************************/ 00246 00247 /****************************************************************************** 00248 * Metaserver2 support starts here. 00249 * 00250 ******************************************************************************/ 00251 00252 pthread_mutex_t ms2_info_mutex; 00253 00254 /* we use threads so that the GUI keeps responding while we wait for 00255 * data. But we need to note if the thread is running or not, 00256 * so we store it here. This, like the other metaserver2 data, 00257 * should be protected by using the ms2_info_mutext. 00258 */ 00259 static int ms2_is_running=0; 00260 00261 /* list of metaserver URL to get information from - this should generally 00262 * correspond to the value in the metaserver2 server file, but instead 00263 * of meta_update.php, use meta_client.php. 00264 * 00265 * These could perhaps be in some other file (config.h or the like), but 00266 * it seems unlikely that these will change very often, and certainly not 00267 * at a level where we would expect users to go about changing the values. 00268 */ 00269 static char *metaservers[] = {"http://crossfire.real-time.com/metaserver2/meta_client.php"}; 00270 00295 size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data) 00296 { 00297 #ifdef HAVE_CURL_CURL_H 00298 size_t realsize = size * nmemb; 00299 char *cp, *newline, *eq, inbuf[CURL_MAX_WRITE_SIZE*2+1], *leftover; 00300 00301 leftover = (char*) data; 00302 00303 if (realsize > CURL_MAX_WRITE_SIZE) { 00304 LOG(LOG_CRITICAL, "common::metaserver2_writer", "Function called with more data than allowed!"); 00305 } 00306 00307 /* This memcpy here is to just give us a null terminated character 00308 * array - easier to do with than having to check lengths as well as other 00309 * values. Also, it makes it easier to deal with unprocessed data from 00310 * the last call. 00311 */ 00312 memcpy(inbuf, leftover, strlen(leftover)); 00313 memcpy(inbuf+strlen(leftover), ptr, realsize); 00314 inbuf[strlen(leftover)+realsize] = 0; 00315 leftover[0] =0; 00316 00317 /* Processing this block of data shouldn't take very long, even on 00318 * slow machines, so putting the lock here, instead of each time 00319 * we update a variable is cleaner 00320 */ 00321 pthread_mutex_lock(&ms2_info_mutex); 00322 00323 for (cp = inbuf; cp != NULL && *cp!=0; cp=newline) { 00324 newline=strchr(cp, '\n'); 00325 if (newline) { 00326 *newline = 0; 00327 newline++; 00328 } else { 00329 /* If we didn't get a newline, then this is the 00330 * end of the block of data for this call - store 00331 * away the extra for the next call. 00332 */ 00333 strncpy(leftover, cp, CURL_MAX_WRITE_SIZE-1); 00334 leftover[CURL_MAX_WRITE_SIZE-1] = 0; 00335 break; 00336 } 00337 00338 eq = strchr(cp,'='); 00339 if (eq) { 00340 *eq = 0; 00341 eq++; 00342 } 00343 00344 if (!strcmp(cp, "START_SERVER_DATA")) { 00345 /* Clear out all data - MS2 doesn't necessarily use all the 00346 * fields, so blank out any that we are not using. 00347 */ 00348 memset(&meta_servers[meta_numservers], 0, sizeof(Meta_Info)); 00349 } 00350 else if (!strcmp(cp, "END_SERVER_DATA")) { 00351 int i; 00352 00353 /* we can get data from both metaserver1 & 2 - no reason to keep 00354 * both. So check for duplicates, and consider metaserver2 00355 * data 'better'. 00356 */ 00357 for (i=0; i<meta_numservers; i++) { 00358 if (!strcasecmp(meta_servers[i].hostname, meta_servers[meta_numservers].hostname)) { 00359 memcpy(&meta_servers[i], &meta_servers[meta_numservers], sizeof(Meta_Info)); 00360 break; 00361 } 00362 } 00363 if (i>=meta_numservers) { 00364 meta_numservers++; 00365 } 00366 } else { 00367 /* If we get here, these should be variable=value pairs. 00368 * if we don't have a value, can't do anything, and 00369 * report an error. This would normally be incorrect 00370 * data from the server. 00371 */ 00372 if (!eq) { 00373 LOG(LOG_ERROR, "common::metaserver2_writer", "Unknown line: %s",cp); 00374 continue; 00375 } 00376 if (!strcmp(cp,"hostname")) { 00377 strncpy(meta_servers[meta_numservers].hostname, eq, sizeof(meta_servers[meta_numservers].hostname)); 00378 } 00379 else if (!strcmp(cp,"port")) { 00380 meta_servers[meta_numservers].port = atoi(eq); 00381 } 00382 else if (!strcmp(cp,"html_comment")) { 00383 strncpy(meta_servers[meta_numservers].html_comment, eq, sizeof(meta_servers[meta_numservers].html_comment)); 00384 } 00385 else if (!strcmp(cp,"text_comment")) { 00386 strncpy(meta_servers[meta_numservers].text_comment, eq, sizeof(meta_servers[meta_numservers].text_comment)); 00387 } 00388 else if (!strcmp(cp,"archbase")) { 00389 strncpy(meta_servers[meta_numservers].archbase, eq, sizeof(meta_servers[meta_numservers].archbase)); 00390 } 00391 else if (!strcmp(cp,"mapbase")) { 00392 strncpy(meta_servers[meta_numservers].mapbase, eq, sizeof(meta_servers[meta_numservers].mapbase)); 00393 } 00394 else if (!strcmp(cp,"codebase")) { 00395 strncpy(meta_servers[meta_numservers].codebase, eq, sizeof(meta_servers[meta_numservers].codebase)); 00396 } 00397 else if (!strcmp(cp,"flags")) { 00398 strncpy(meta_servers[meta_numservers].flags, eq, sizeof(meta_servers[meta_numservers].flags)); 00399 } 00400 else if (!strcmp(cp,"version")) { 00401 strncpy(meta_servers[meta_numservers].version, eq, sizeof(meta_servers[meta_numservers].version)); 00402 } 00403 else if (!strcmp(cp,"num_players")) { 00404 meta_servers[meta_numservers].num_players = atoi(eq); 00405 } 00406 else if (!strcmp(cp,"in_bytes")) { 00407 meta_servers[meta_numservers].in_bytes = atoi(eq); 00408 } 00409 else if (!strcmp(cp,"out_bytes")) { 00410 meta_servers[meta_numservers].out_bytes = atoi(eq); 00411 } 00412 else if (!strcmp(cp,"uptime")) { 00413 meta_servers[meta_numservers].uptime = atoi(eq); 00414 } 00415 else if (!strcmp(cp,"sc_version")) { 00416 meta_servers[meta_numservers].sc_version = atoi(eq); 00417 } 00418 else if (!strcmp(cp,"cs_version")) { 00419 meta_servers[meta_numservers].cs_version = atoi(eq); 00420 } 00421 else if (!strcmp(cp,"last_update")) { 00422 /* MS2 reports update time as when it last got an update, 00423 * where as we want actual elapsed time since last update. 00424 * So do the conversion. Second check is because of clock 00425 * skew - my clock may be fast, and we don't want negative times. 00426 */ 00427 meta_servers[meta_numservers].idle_time = time(NULL) - atoi(eq); 00428 if (meta_servers[meta_numservers].idle_time < 0) 00429 meta_servers[meta_numservers].idle_time = 0; 00430 } 00431 else { 00432 LOG(LOG_ERROR, "common::metaserver2_writer", "Unknown line: %s=%s",cp,eq); 00433 } 00434 } 00435 } 00436 pthread_mutex_unlock(&ms2_info_mutex); 00437 return realsize; 00438 #else 00439 return 0; 00440 #endif 00441 } 00442 00451 static int get_metaserver2_data(char *metaserver2) { 00452 #ifdef HAVE_CURL_CURL_H 00453 CURL *curl; 00454 CURLcode res; 00455 char leftover[CURL_MAX_WRITE_SIZE]; 00456 00457 curl = curl_easy_init(); 00458 if (!curl) return 0; 00459 leftover[0] =0; 00460 curl_easy_setopt(curl, CURLOPT_URL, metaserver2); 00461 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer); 00462 curl_easy_setopt(curl, CURLOPT_WRITEDATA, leftover); 00463 res = curl_easy_perform(curl); 00464 curl_easy_cleanup(curl); 00465 00466 if (res) return 0; 00467 else return 1; 00468 #else 00469 return 1; 00470 #endif 00471 } 00472 00479 void *metaserver2_thread(void *junk) 00480 { 00481 int metaserver_choice, tries=0; 00482 00483 do { 00484 metaserver_choice = random() % (sizeof(metaservers) / sizeof(char*)); 00485 tries++; 00486 if (tries>5) break; 00487 } while (!get_metaserver2_data(metaservers[metaserver_choice])); 00488 00489 pthread_mutex_lock(&ms2_info_mutex); 00490 qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort); 00491 ms2_is_running=0; 00492 pthread_mutex_unlock(&ms2_info_mutex); 00493 pthread_exit(NULL); 00494 // never reached, just to make the compiler happy. 00495 return NULL; 00496 } 00497 00498 00507 int metaserver2_get_info(void) { 00508 pthread_t thread_id; 00509 int ret; 00510 00511 if (!metaserver2_on) { 00512 return 0; 00513 } 00514 #ifndef HAVE_CURL_CURL_H 00515 return 0; 00516 #endif 00517 00518 metaserver_load_cache(); 00519 00520 pthread_mutex_lock(&ms2_info_mutex); 00521 if (!meta_servers) 00522 meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info)); 00523 00524 ms2_is_running=1; 00525 pthread_mutex_unlock(&ms2_info_mutex); 00526 00527 ret=pthread_create(&thread_id, NULL, metaserver2_thread, NULL); 00528 if (ret) { 00529 LOG(LOG_ERROR, "common::metaserver2_get_info", "Thread creation failed."); 00530 pthread_mutex_lock(&ms2_info_mutex); 00531 ms2_is_running=0; 00532 pthread_mutex_unlock(&ms2_info_mutex); 00533 } 00534 00535 return 0; 00536 } 00537 00541 void init_metaserver(void) 00542 { 00543 pthread_mutex_init(&ms2_info_mutex, NULL); 00544 #ifdef HAVE_CURL_CURL_H 00545 curl_global_init(CURL_GLOBAL_ALL); 00546 #endif 00547 } 00548 00549 /****************************************************************************** 00550 * End of Metasever2 functions. 00551 ******************************************************************************/ 00552 00553 /****************************************************************************** 00554 * Start of metaserver1 logic 00555 * 00556 * Note that this shares the same mutex as metaserver2, since it is updating 00557 * most of the same structures. 00558 *******************************************************************************/ 00559 00560 static int ms1_is_running=0; 00561 00562 00563 #ifdef WIN32 00564 /* Need script.h for script_killall */ 00565 #include <script.h> 00566 00571 char *get_line_from_sock(char *s, size_t n, int fd) { 00572 static long charsleft = 0; 00573 static char inbuf[MS_LARGE_BUF*4]; 00574 char *cp; 00575 int ct; 00576 00577 if (!s) 00578 return s; 00579 if (n != MS_LARGE_BUF*4-1) { 00580 LOG(LOG_CRITICAL, "common::get_line_from_sock", "Serious program logic error in get_line_from_sock()."); 00581 exit(-1); 00582 } 00583 00584 if (charsleft > MS_LARGE_BUF*4-3 && strchr(inbuf, '\n') == NULL) { 00585 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00586 "Metaserver returned an overly long line."); 00587 return NULL; 00588 } 00589 00590 /* If there is no line in the buffer */ 00591 while (charsleft == 0 || (cp = strchr(inbuf, '\n')) == NULL) { 00592 FD_SET fdset; 00593 TIMEVAL tv = {3, 0}; /* 3 second timeout on reads */ 00594 int nlen; 00595 FD_ZERO(&fdset); 00596 FD_SET(fd, &fdset); 00597 if (select(0, &fdset, NULL, NULL, &tv) == 0) { 00598 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00599 "Metaserver timed out."); 00600 return NULL; 00601 } 00602 00603 nlen = recv(fd, inbuf+charsleft-1, MS_LARGE_BUF*4-1-charsleft, 0); 00604 if (nlen == SOCKET_ERROR || nlen <= 0) /* Probably EOF */ 00605 return NULL; 00606 00607 charsleft += nlen; 00608 } 00609 00610 /* OK, inbuf contains a null terminated string with at least one \n 00611 * Copy the string up to the \n to s, and then move the rest of the 00612 * inbuf string to the beginning of the buffer. And finally, set 00613 * charsleft to the number of characters left in inbuf, or 0. 00614 * Oh, and cp contains the location of the \n. 00615 */ 00616 00617 memcpy(s, inbuf, cp-inbuf+1); /* Extract the line, including the \n. */ 00618 s[cp-inbuf+1] = 0; /* null terminate it */ 00619 00620 /* Copy cp to inbuf up to the \0, (skipping the \n) */ 00621 ct = 0; 00622 while (cp[++ct] != 0) { 00623 inbuf[ct-1] = cp[ct]; 00624 } 00625 inbuf[ct-1] = 0; 00626 charsleft = ct; /* And keep track of how many characters are left. */ 00627 00628 return s; 00629 } 00630 00631 #endif /* Win32 */ 00632 00636 void *metaserver1_thread(void *junk) 00637 { 00638 struct protoent *protox; 00639 int fd; 00640 struct sockaddr_in insock; 00641 #ifndef WIN32 00642 FILE *fp; 00643 #endif 00644 char inbuf[MS_LARGE_BUF*4]; 00645 Meta_Info *current; 00646 00647 protox = getprotobyname("tcp"); 00648 if (protox == NULL) { 00649 LOG(LOG_WARNING, "common::metaserver_get_info", "Error getting protobyname (tcp)"); 00650 pthread_mutex_lock(&ms2_info_mutex); 00651 ms1_is_running=0; 00652 pthread_mutex_unlock(&ms2_info_mutex); 00653 pthread_exit(NULL); 00654 } 00655 00656 fd = socket(PF_INET, SOCK_STREAM, protox->p_proto); 00657 if (fd == -1) { 00658 perror("get_metaserver_info: Error on socket command.\n"); 00659 pthread_mutex_lock(&ms2_info_mutex); 00660 ms1_is_running=0; 00661 pthread_mutex_unlock(&ms2_info_mutex); 00662 pthread_exit(NULL); 00663 } 00664 insock.sin_family = AF_INET; 00665 insock.sin_port = htons((unsigned short)meta_port); 00666 if (isdigit(*meta_server)) 00667 insock.sin_addr.s_addr = inet_addr(meta_server); 00668 else { 00669 struct hostent *hostbn = gethostbyname(meta_server); 00670 if (hostbn == NULL) { 00671 LOG(LOG_WARNING, "common::metaserver_get_info", "Unknown metaserver hostname: %s", meta_server); 00672 pthread_mutex_lock(&ms2_info_mutex); 00673 ms1_is_running=0; 00674 pthread_mutex_unlock(&ms2_info_mutex); 00675 pthread_exit(NULL); 00676 } 00677 memcpy(&insock.sin_addr, hostbn->h_addr, hostbn->h_length); 00678 } 00679 if (connect(fd, (struct sockaddr *)&insock, sizeof(insock)) == -1) { 00680 perror("Can't connect to metaserver"); 00681 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00682 "\nCan't connect to metaserver."); 00683 pthread_mutex_lock(&ms2_info_mutex); 00684 ms1_is_running=0; 00685 pthread_mutex_unlock(&ms2_info_mutex); 00686 pthread_exit(NULL); 00687 } 00688 00689 #ifndef WIN32 /* Windows doesn't support this */ 00690 /* Turn this into a file handle - this will break it on newlines 00691 * for us, which makes our processing much easier - it basically 00692 * means one line/server 00693 */ 00694 if ((fp = fdopen(fd, "r")) == NULL) { 00695 perror("fdopen failed."); 00696 pthread_mutex_lock(&ms2_info_mutex); 00697 ms1_is_running=0; 00698 pthread_mutex_unlock(&ms2_info_mutex); 00699 pthread_exit(NULL); 00700 } 00701 #endif 00702 00703 pthread_mutex_lock(&ms2_info_mutex); 00704 if (!meta_servers) 00705 meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info)); 00706 00707 00708 /* The loop goes through and unpacks the data from the metaserver 00709 * into its individual components. We do a little extra work and 00710 * put the |'s back in the string after we are done with that section - 00711 * this is so if there is a corrupt entry, it gets displayed as 00712 * originally received from the server. 00713 */ 00714 #ifndef WIN32 /* Windows doesn't support this */ 00715 while (fgets(inbuf, MS_LARGE_BUF*4-1, fp) != NULL) { 00716 #else 00717 while (get_line_from_sock(inbuf, MS_LARGE_BUF*4-1, fd) != NULL) { 00718 #endif 00719 char *cp, *cp1; 00720 00721 cp = strchr(inbuf, '|'); 00722 if (cp == NULL) { 00723 LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf); 00724 break; 00725 } 00726 *cp = 0; 00727 00728 current = &meta_servers[meta_numservers]; 00729 00730 strncpy(current->ip_addr, inbuf, sizeof(current->ip_addr)-1); 00731 current->ip_addr[sizeof(current->ip_addr)-1] = '\0'; 00732 *cp++ = '|'; 00733 00734 current->idle_time = atoi(cp); 00735 00736 cp1 = strchr(cp, '|'); 00737 if (cp1 == NULL) { 00738 LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf); 00739 break; 00740 } 00741 *cp1 = 0; 00742 00743 cp = strchr(cp1+1, '|'); 00744 if (cp == NULL) { 00745 LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf); 00746 break; 00747 } 00748 *cp = 0; 00749 /* cp1 points at start of comment, cp points at end */ 00750 strncpy(current->hostname, cp1+1, sizeof(current->hostname)-1); 00751 current->hostname[sizeof(current->hostname)-1] = '\0'; 00752 00753 *cp1++ = '|'; 00754 *cp++ = '|'; /* cp now points to num players */ 00755 00756 current->num_players = atoi(cp); 00757 00758 cp1 = strchr(cp, '|'); 00759 if (cp1 == NULL) { 00760 LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf); 00761 break; 00762 } 00763 *cp1 = 0; 00764 00765 cp = strchr(cp1+1, '|'); 00766 if (cp == NULL) { 00767 LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf); 00768 break; 00769 } 00770 *cp = 0; 00771 /* cp1 is start of version, cp is end */ 00772 strncpy(current->version, cp1+1, sizeof(current->version)-1); 00773 current->version[sizeof(current->version)-1] = '\0'; 00774 00775 *cp1++ = '|'; 00776 *cp++ = '|'; /* cp now points to comment */ 00777 00778 cp1 = strchr(cp, '\n'); 00779 if (cp1 == NULL) { 00780 LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf); 00781 break; 00782 } 00783 *cp1 = 0; 00784 /* There is extra info included, like the bytes to/from the server 00785 * that we dont' care about, so strip them off so they don't show up in 00786 * the comment. 00787 */ 00788 cp1 = strchr(cp, '|'); 00789 if (cp1 != NULL) 00790 *cp1 = 0; 00791 00792 strncpy(current->text_comment, cp, sizeof(current->text_comment)-1); 00793 current->text_comment[sizeof(current->text_comment)-1] = '\0'; 00794 00795 meta_numservers++; 00796 /* has to be 1 less than array size, since array starts counting 00797 * at 0. 00798 */ 00799 if (meta_numservers >= MAX_METASERVER-1) { 00800 LOG(LOG_WARNING, "common:metaserver_get_info", "Have reached maximum metaserver count\n"); 00801 break; 00802 } 00803 } 00804 #ifdef WIN32 00805 closesocket(fd); 00806 #else 00807 fclose(fp); 00808 #endif 00809 qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort); 00810 ms1_is_running=0; 00811 pthread_mutex_unlock(&ms2_info_mutex); 00812 pthread_exit(NULL); 00813 /* never reached, just to make the compiler happy. */ 00814 return NULL; 00815 } 00816 00820 int metaserver1_get_info(void) { 00821 pthread_t thread_id; 00822 int ret; 00823 00824 if (!metaserver_on) { 00825 return 0; 00826 } 00827 metaserver_load_cache(); 00828 00829 pthread_mutex_lock(&ms2_info_mutex); 00830 if (!meta_servers) 00831 meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info)); 00832 00833 ms1_is_running=1; 00834 pthread_mutex_unlock(&ms2_info_mutex); 00835 00836 ret=pthread_create(&thread_id, NULL, metaserver1_thread, NULL); 00837 if (ret) { 00838 LOG(LOG_ERROR, "common::metaserver1_get_info", "Thread creation failed."); 00839 pthread_mutex_lock(&ms2_info_mutex); 00840 ms1_is_running=0; 00841 pthread_mutex_unlock(&ms2_info_mutex); 00842 } 00843 00844 return 0; 00845 } 00846 /****************************************************************************** 00847 * End of metaserver1 logic 00848 ******************************************************************************/ 00849 00850 /****************************************************************************** 00851 * This is start of common logic - the above sections are actually getting 00852 * the data. The code below here is just displaying the data we got 00853 */ 00854 00865 int metaserver_check_status(void) { 00866 int status; 00867 00868 pthread_mutex_lock(&ms2_info_mutex); 00869 status = ms2_is_running | ms1_is_running; 00870 pthread_mutex_unlock(&ms2_info_mutex); 00871 00872 return status; 00873 } 00874 00882 int metaserver_get_info(char *metaserver, int meta_port) { 00883 00884 meta_numservers = 0; 00885 00886 metaserver2_get_info(); 00887 00888 if (metaserver_on) { 00889 metaserver1_get_info(); 00890 } 00891 00892 return 0; 00893 } 00894 00899 void metaserver_show(int show_selection) { 00900 int i; 00901 char buf[256]; 00902 00903 if (cached_servers_num) { 00904 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00905 "\nLast servers you connected to:\n"); 00906 for (i = 0; i < cached_servers_num; i++) { 00907 snprintf(buf, sizeof(buf), "%2d) %-20.20s %-20.20s", i+1, cached_servers_name[i], cached_servers_ip[i]); 00908 draw_ext_info( 00909 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf); 00910 } 00911 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, " "); 00912 } 00913 00914 while(metaserver_check_status()) { 00915 usleep(100); 00916 } 00917 00918 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00919 " #) Server # version idle"); 00920 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00921 " Name players seconds"); 00922 pthread_mutex_lock(&ms2_info_mutex); 00923 00924 /* Re-sort the data - may get different data from ms1 and ms2, so 00925 * order of this is somewhat random. 00926 */ 00927 qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort); 00928 for (i = 0; i < meta_numservers; i++) { 00929 if (check_server_version(i)) { 00930 snprintf(buf, sizeof(buf), "%2d) %-15.15s %2d %-12.12s %2d", 00931 i+1+cached_servers_num, meta_servers[i].hostname, 00932 meta_servers[i].num_players, meta_servers[i].version, 00933 meta_servers[i].idle_time); 00934 draw_ext_info( 00935 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf); 00936 } 00937 } 00938 if (show_selection) { 00939 /* Show default/current server */ 00940 if (server) { 00941 snprintf(buf, sizeof(buf), "%2d) %s (default)", meta_numservers+1+cached_servers_num, server); 00942 draw_ext_info( 00943 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf); 00944 } 00945 00946 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00947 "Choose one of the entries above"); 00948 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00949 "or type in a hostname/ip address"); 00950 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00951 "Hit enter to re-update this list"); 00952 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 00953 "Enter 0 to exit the program."); 00954 } 00955 pthread_mutex_unlock(&ms2_info_mutex); 00956 } 00957 00964 int metaserver_select(char *sel) { 00965 int num = atoi(sel); 00966 int port=0; 00967 char buf[MAX_BUF], buf2[MAX_BUF]; 00968 char *server_name = NULL, *server_ip; 00969 00970 /* User hit return */ 00971 if (sel[0] == 0) { 00972 metaserver_get_info(meta_server, meta_port); 00973 metaserver_show(TRUE); 00974 return 1; 00975 } 00976 00977 /* Special case - player really entered a 0, so exit the 00978 * program. 00979 */ 00980 if (num == 0 && sel[0] == '0') { 00981 #ifdef WIN32 00982 script_killall(); 00983 #endif 00984 exit(0); 00985 } 00986 00987 pthread_mutex_lock(&ms2_info_mutex); 00988 00989 /* if the entry is not a number (selection from the list), 00990 * or is a selection but also has a dot (suggesting 00991 * a.b.c.d selection), just try to connect with given name. 00992 */ 00993 if (num == 0 || strchr(sel, '.') != NULL) { 00994 server_name = sel; 00995 server_ip = sel; 00996 } else { 00997 if (num <= 0 || num > meta_numservers+cached_servers_num+1) { 00998 draw_ext_info( 00999 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 01000 "Invalid selection. Try again"); 01001 return 1; 01002 } 01003 01004 if (num == meta_numservers+cached_servers_num+1) { 01005 server_name = server; 01006 server_ip = server; 01007 } else if (num > cached_servers_num) { 01008 server_name = meta_servers[num-cached_servers_num-1 ].hostname; 01009 server_ip = meta_servers[num-cached_servers_num-1 ].ip_addr; 01010 port = meta_servers[num-cached_servers_num-1 ].port; 01011 } else { 01012 server_name = cached_servers_name[num-1]; 01013 server_ip = cached_servers_ip[num-1]; 01014 } 01015 } 01016 pthread_mutex_unlock(&ms2_info_mutex); 01017 if (!server_name) { 01018 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 01019 "Bad selection. Try again"); 01020 return 1; 01021 } 01022 01023 /* check for :port suffix, and use it */ 01024 if (!port) { 01025 if ((sel = strrchr(server_name, ':')) != NULL && (port = atoi(sel+1)) > 0) { 01026 snprintf(buf2, sizeof(buf2), "%s", server_name); 01027 buf2[sel-server_name] = '\0'; 01028 server_name = buf2; 01029 } 01030 else { 01031 port = use_config[CONFIG_PORT]; 01032 } 01033 } 01034 01035 snprintf(buf, sizeof(buf), "Trying to connect to %s:%d", server_name, port); 01036 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf); 01037 #ifdef MULTKEYS 01038 csocket.fd = init_connection(server_name, port); 01039 #else 01040 csocket.fd = init_connection(server_ip, port); 01041 #endif 01042 if (csocket.fd == -1) { 01043 draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, 01044 "Unable to connect to server."); 01045 return 1; 01046 } 01047 01048 /* 01049 * Upon successful connection, add the server to the cache or move it to 01050 * the top of the list. 01051 */ 01052 if ((num <= meta_numservers) 01053 && (num != meta_numservers + cached_servers_num + 1)) 01054 metaserver_update_cache(server_name, server_ip); 01055 01056 return 0; 01057 } 01058 01059 #ifdef MS_STANDALONE 01060 /* This is here just to verify that the code seems to be working 01061 * properly, this tests both metaserver one and metaserver2 01062 * To use this code, compile as: 01063 * gcc -o metaserver -I. -DMS_STANDALONE metaserver.c misc.o -lcurl -lpthread 01064 * if you only want to have support for one type of server then use either 01065 * -DMS_SA_NOTMS1 or -DMS_SA_NOTMS2 to disable the metaserver you don't want. 01066 * The list of servers goes to stdout, the headers for the tables, status messages etc, go to stderr. 01067 */ 01068 01069 /* Following lines are to cover external symbols not 01070 * defined - trying to bring in the files the are defined 01071 * in just causes more dependencies, etc. 01072 */ 01073 void draw_ext_info(int orig_color, int type, int subtype, const char *message) {} 01074 int init_connection(char *host, int port) {} 01075 01076 int metaserver2_on=1, metaserver_on=1; 01077 char *server=NULL; 01078 sint16 use_config[CONFIG_NUMS]; 01079 ClientSocket csocket; 01080 char *meta_server=META_SERVER; 01081 int meta_port=META_PORT; 01082 01086 void handle_ms_data(int msservernum) { 01087 int i; 01088 fprintf(stderr,"Collecting data from metaserver %d.", msservernum); 01089 while (metaserver_check_status()) { 01090 fprintf(stderr,"."); 01091 sleep(1); 01092 } 01093 fprintf(stderr, "\nIp Address:Idle Time:Hostname:Players:Version:Comment\n"); 01094 for (i = 0; i < meta_numservers; i++) { 01095 printf("%s:%d:%s:%d:%s:%s\n", 01096 meta_servers[i].ip_addr, 01097 meta_servers[i].idle_time, 01098 meta_servers[i].hostname, 01099 meta_servers[i].num_players, 01100 meta_servers[i].version, 01101 meta_servers[i].text_comment); 01102 } 01103 fprintf(stderr, "%d servers found\n", meta_numservers); 01104 } 01105 01109 int main(int argc, char *argv[]) { 01110 01111 #ifdef MS_SA_NOTMS2 01112 metaserver2_on=0; 01113 #endif 01114 #ifdef MS_SA_NOTMS1 01115 metaserver_on=0; 01116 #endif 01117 01118 init_metaserver(); 01119 if(metaserver2_on) { 01120 metaserver2_get_info(); 01121 handle_ms_data(2); 01122 } 01123 /* both metaservers use the same array to store the servers in, so we'll 01124 * reset it here in order to get the results from the other metaserver. */ 01125 free(meta_servers); 01126 meta_servers=NULL; 01127 meta_numservers = 0; 01128 if(metaserver_on) { 01129 metaserver1_get_info(); 01130 handle_ms_data(1); 01131 } 01132 } 01133 01134 #endif
1.7.6.1