Crossfire Client, Trunk  R18666
/home/leaf/crossfire/client/trunk/common/metaserver.c
Go to the documentation of this file.
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