Crossfire Client, Trunk  R19307
metaserver.c
Go to the documentation of this file.
00001 /*
00002  * Crossfire -- cooperative multi-player graphical RPG and adventure game
00003  *
00004  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
00005  * Copyright (c) 1992 Frank Tore Johansen
00006  *
00007  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
00008  * welcome to redistribute it under certain conditions. For details, please
00009  * see COPYING and LICENSE.
00010  *
00011  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
00012  */
00013 
00021 #ifndef WIN32
00022 #include <netdb.h>
00023 #include <sys/types.h>
00024 #include <sys/socket.h>
00025 #include <netinet/in.h>
00026 #include <arpa/inet.h>
00027 #endif /* WIN32 */
00028 
00029 #include <ctype.h>
00030 #include <stdio.h>
00031 #include <stdlib.h>
00032 
00033 #include <client.h>
00034 #include <cconfig.h>
00035 #include <external.h>
00036 
00037 #include <metaserver.h>
00038 
00039 #ifdef HAVE_CURL_CURL_H
00040 #include <curl/curl.h>
00041 #include <curl/easy.h>
00042 #endif
00043 
00044 Meta_Info *meta_servers = NULL;
00045 
00046 int meta_numservers = 0;
00047 
00048 int meta_sort(Meta_Info *m1, Meta_Info *m2)
00049 {
00050     return strcasecmp(m1->hostname, m2->hostname);
00051 }
00052 
00064 int check_server_version(int entry)
00065 {
00066 
00067     /* No version information - nothing to do. */
00068     if (!meta_servers[entry].sc_version || !meta_servers[entry].cs_version) {
00069         return 1;
00070     }
00071 
00072     if (meta_servers[entry].sc_version != VERSION_SC) {
00073         /* 1027->1028 removed a bunch of old commands, so a 1028
00074          * version client can still play on a 1027 server, so
00075          * special hard code that.
00076          *
00077          * Likewise, 1028->1029 just changed how weapon_speed
00078          * should be interperted on the client - the client
00079          * does the right thing, so not problem with a 1029
00080          * client playing on 1028 or 1027 server.
00081          *
00082          * A 1028 client could in practice play on a 1029
00083          * server, since at the protocol level, data is the same -
00084          * the client would just have screwed up weapon_sp values.
00085          */
00086         if ((VERSION_SC == 1028 || VERSION_SC==1029) &&
00087                 (meta_servers[entry].sc_version==1027 ||
00088                  meta_servers[entry].sc_version==1028)) {
00089             return 1;
00090         }
00091     }
00092     if (meta_servers[entry].cs_version != VERSION_CS) {
00093         return 0;
00094     }
00095 
00096     return 1;
00097 }
00098 
00099 /*****************************************************************************
00100  * Start of cache related functions.
00101  *****************************************************************************/
00102 int cached_servers_num = 0;
00103 char *cached_servers_name[CACHED_SERVERS_MAX];
00104 char *cached_servers_ip[CACHED_SERVERS_MAX];
00105 static int cached_servers_loaded = 0;
00106 const char *cached_server_file = NULL;
00107 
00122 static void metaserver_load_cache(void)
00123 {
00124     char name[MS_LARGE_BUF], ip[MS_LARGE_BUF];
00125     FILE *cache;
00126 
00127     if (cached_servers_loaded || !cached_server_file) {
00128         return;
00129     }
00130 
00131     /* If failure, we don't want to load again */
00132     cached_servers_loaded = 1;
00133     cached_servers_num = 0;
00134 
00135     cache = fopen(cached_server_file, "r");
00136     if (!cache) {
00137         return;
00138     }
00139 
00140     while (cached_servers_num < CACHED_SERVERS_MAX
00141             &&     fgets(name, MS_LARGE_BUF, cache) != NULL
00142             &&     fgets(ip  , MS_LARGE_BUF, cache) != NULL) {
00143         ip[strlen(ip)-1] = 0;
00144         name[strlen(name)-1] = 0;
00145         cached_servers_ip[cached_servers_num] = strdup(ip);
00146         cached_servers_name[cached_servers_num++] = strdup(name);
00147     }
00148     fclose(cache);
00149 }
00150 
00154 static void metaserver_save_cache(void)
00155 {
00156     FILE *cache;
00157     int server;
00158 
00159     if (!cached_server_file) {
00160         return;
00161     }
00162 
00163     cache = fopen(cached_server_file, "w");
00164     if (!cache) {
00165         return;
00166     }
00167 
00168     for (server = 0; server < cached_servers_num; server++) {
00169         fprintf(cache, "%s\n", cached_servers_name[server]);
00170         fprintf(cache, "%s\n", cached_servers_ip[server]);
00171     }
00172     fclose(cache);
00173 }
00174 
00180 void metaserver_update_cache(const char *server_name, const char *server_ip)
00181 {
00182     int index;
00183 
00184     /*
00185      * Try to find the given server name in the existing server cache.  If the
00186      * zero-based index ends up equal to the one-based number of cached
00187      * servers, it was not found.
00188      */
00189     for (index = 0; index < cached_servers_num; index++) {
00190         if (strcmp(server_name, cached_servers_name[index]) == 0) {
00191             break;
00192         }
00193     }
00194 
00195     /*
00196      * If server is already first in the cache list, nothing else needs to be
00197      * done, otherwise, the server needs to be cached.
00198      */
00199     if (index != 0 || !cached_servers_num) {
00200         char *name;
00201         char *ip;
00202         int  copy;
00203 
00204         if (index == cached_servers_num) {
00205             /*
00206              * If the server was not found in the cache, expand the cache size
00207              * by one unless that creates too many entries.
00208              */
00209             name = strdup(server_name);
00210             ip = strdup(server_ip);
00211             cached_servers_num++;
00212             if (cached_servers_num > CACHED_SERVERS_MAX) {
00213                 cached_servers_num--;
00214                 free(cached_servers_name[cached_servers_num-1]);
00215                 free(cached_servers_ip[cached_servers_num-1]);
00216             }
00217         } else {
00218             /*
00219              * If the server was already listed in the cache, grab a copy of
00220              * the prior listing.
00221              */
00222             name = cached_servers_name[index];
00223             ip = cached_servers_ip[index];
00224         }
00225 
00226         /*
00227          * If the server as already listed, move all the cached items above
00228          * the listing down a slot, otherwise, move the whole list down a
00229          * notch.  This "empties" the top slot.
00230          */
00231         for (copy = MIN(index, CACHED_SERVERS_MAX-1); copy > 0; copy--) {
00232             cached_servers_name[copy] = cached_servers_name[copy-1];
00233             cached_servers_ip[copy] = cached_servers_ip[copy-1];
00234         }
00235 
00236         /*
00237          * Put the added server information at the top of the cache list, and
00238          * save the changes.
00239          */
00240         cached_servers_name[0] = name;
00241         cached_servers_ip[0] = ip;
00242         metaserver_save_cache();
00243     }
00244 }
00245 
00246 /*****************************************************************************
00247  * End of cache related functions.
00248  *****************************************************************************/
00249 
00250 /******************************************************************************
00251  * Metaserver2 support starts here.
00252  *
00253  ******************************************************************************/
00254 
00255 pthread_mutex_t ms2_info_mutex;
00256 
00257 /* we use threads so that the GUI keeps responding while we wait for
00258  * data.  But we need to note if the thread is running or not,
00259  * so we store it here.  This, like the other metaserver2 data,
00260  * should be protected by using the ms2_info_mutext.
00261  */
00262 static int ms2_is_running=0;
00263 
00264 /* list of metaserver URL to get information from - this should generally
00265  * correspond to the value in the metaserver2 server file, but instead
00266  * of meta_update.php, use meta_client.php.
00267  *
00268  * These could perhaps be in some other file (config.h or the like), but
00269  * it seems unlikely that these will change very often, and certainly not
00270  * at a level where we would expect users to go about changing the values.
00271  */
00272 static char *metaservers[] = {"http://crossfire.real-time.com/metaserver2/meta_client.php"};
00273 
00298 size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data)
00299 {
00300 #ifdef HAVE_CURL_CURL_H
00301     size_t realsize = size * nmemb;
00302     char    *cp, *newline, *eq, inbuf[CURL_MAX_WRITE_SIZE*2+1], *leftover;
00303 
00304     leftover = (char*) data;
00305 
00306     if (realsize > CURL_MAX_WRITE_SIZE) {
00307         LOG(LOG_CRITICAL, "common::metaserver2_writer", "Function called with more data than allowed!");
00308     }
00309 
00310     /* This memcpy here is to just give us a null terminated character
00311      * array - easier to do with than having to check lengths as well as other
00312      * values.  Also, it makes it easier to deal with unprocessed data from
00313      * the last call.
00314      */
00315     memcpy(inbuf, leftover, strlen(leftover));
00316     memcpy(inbuf+strlen(leftover), ptr, realsize);
00317     inbuf[strlen(leftover)+realsize] = 0;
00318     leftover[0] =0;
00319 
00320     /* Processing this block of data shouldn't take very long, even on
00321      * slow machines, so putting the lock here, instead of each time
00322      * we update a variable is cleaner
00323      */
00324     pthread_mutex_lock(&ms2_info_mutex);
00325 
00326     for (cp = inbuf; cp != NULL && *cp!=0; cp=newline) {
00327         newline=strchr(cp, '\n');
00328         if (newline) {
00329             *newline = 0;
00330             newline++;
00331         } else {
00332             /* If we didn't get a newline, then this is the
00333              * end of the block of data for this call - store
00334              * away the extra for the next call.
00335              */
00336             strncpy(leftover, cp, CURL_MAX_WRITE_SIZE-1);
00337             leftover[CURL_MAX_WRITE_SIZE-1] = 0;
00338             break;
00339         }
00340 
00341         eq = strchr(cp,'=');
00342         if (eq) {
00343             *eq = 0;
00344             eq++;
00345         }
00346 
00347         if (!strcmp(cp, "START_SERVER_DATA")) {
00348             /* Clear out all data - MS2 doesn't necessarily use all the
00349              * fields, so blank out any that we are not using.
00350              */
00351             memset(&meta_servers[meta_numservers], 0, sizeof(Meta_Info));
00352         } else if (!strcmp(cp, "END_SERVER_DATA")) {
00353             int i;
00354 
00355             /* we can get data from both metaserver1 & 2 - no reason to keep
00356              * both.  So check for duplicates, and consider metaserver2
00357              * data 'better'.
00358              */
00359             for (i=0; i<meta_numservers; i++) {
00360                 if (!strcasecmp(meta_servers[i].hostname, meta_servers[meta_numservers].hostname)) {
00361                     memcpy(&meta_servers[i], &meta_servers[meta_numservers], sizeof(Meta_Info));
00362                     break;
00363                 }
00364             }
00365             if (i>=meta_numservers) {
00366                 meta_numservers++;
00367             }
00368         } else {
00369             /* If we get here, these should be variable=value pairs.
00370              * if we don't have a value, can't do anything, and
00371              * report an error.  This would normally be incorrect
00372              * data from the server.
00373              */
00374             if (!eq) {
00375                 LOG(LOG_ERROR, "common::metaserver2_writer", "Unknown line: %s",cp);
00376                 continue;
00377             }
00378             if (!strcmp(cp,"hostname")) {
00379                 strncpy(meta_servers[meta_numservers].hostname, eq, sizeof(meta_servers[meta_numservers].hostname));
00380             } else if (!strcmp(cp,"port")) {
00381                 meta_servers[meta_numservers].port = atoi(eq);
00382             } else if (!strcmp(cp,"html_comment")) {
00383                 strncpy(meta_servers[meta_numservers].html_comment, eq, sizeof(meta_servers[meta_numservers].html_comment));
00384             } else if (!strcmp(cp,"text_comment")) {
00385                 strncpy(meta_servers[meta_numservers].text_comment, eq, sizeof(meta_servers[meta_numservers].text_comment));
00386             } else if (!strcmp(cp,"archbase")) {
00387                 strncpy(meta_servers[meta_numservers].archbase, eq, sizeof(meta_servers[meta_numservers].archbase));
00388             } else if (!strcmp(cp,"mapbase")) {
00389                 strncpy(meta_servers[meta_numservers].mapbase, eq, sizeof(meta_servers[meta_numservers].mapbase));
00390             } else if (!strcmp(cp,"codebase")) {
00391                 strncpy(meta_servers[meta_numservers].codebase, eq, sizeof(meta_servers[meta_numservers].codebase));
00392             } else if (!strcmp(cp,"flags")) {
00393                 strncpy(meta_servers[meta_numservers].flags, eq, sizeof(meta_servers[meta_numservers].flags));
00394             } else if (!strcmp(cp,"version")) {
00395                 strncpy(meta_servers[meta_numservers].version, eq, sizeof(meta_servers[meta_numservers].version));
00396             } else if (!strcmp(cp,"num_players")) {
00397                 meta_servers[meta_numservers].num_players = atoi(eq);
00398             } else if (!strcmp(cp,"in_bytes")) {
00399                 meta_servers[meta_numservers].in_bytes = atoi(eq);
00400             } else if (!strcmp(cp,"out_bytes")) {
00401                 meta_servers[meta_numservers].out_bytes = atoi(eq);
00402             } else if (!strcmp(cp,"uptime")) {
00403                 meta_servers[meta_numservers].uptime = atoi(eq);
00404             } else if (!strcmp(cp,"sc_version")) {
00405                 meta_servers[meta_numservers].sc_version = atoi(eq);
00406             } else if (!strcmp(cp,"cs_version")) {
00407                 meta_servers[meta_numservers].cs_version = atoi(eq);
00408             } else if (!strcmp(cp,"last_update")) {
00409                 /* MS2 reports update time as when it last got an update,
00410                  * where as we want actual elapsed time since last update.
00411                  * So do the conversion.  Second check is because of clock
00412                  * skew - my clock may be fast, and we don't want negative times.
00413                  */
00414                 meta_servers[meta_numservers].idle_time = time(NULL) - atoi(eq);
00415                 if (meta_servers[meta_numservers].idle_time < 0) {
00416                     meta_servers[meta_numservers].idle_time = 0;
00417                 }
00418             } else {
00419                 LOG(LOG_ERROR, "common::metaserver2_writer", "Unknown line: %s=%s",cp,eq);
00420             }
00421         }
00422     }
00423     pthread_mutex_unlock(&ms2_info_mutex);
00424     return realsize;
00425 #else
00426     return 0;
00427 #endif
00428 }
00429 
00438 static int get_metaserver2_data(char *metaserver2)
00439 {
00440 #ifdef HAVE_CURL_CURL_H
00441     CURL *curl;
00442     CURLcode res;
00443     char    leftover[CURL_MAX_WRITE_SIZE];
00444 
00445     curl = curl_easy_init();
00446     if (!curl) {
00447         return 0;
00448     }
00449     leftover[0] =0;
00450     curl_easy_setopt(curl, CURLOPT_URL, metaserver2);
00451     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer);
00452     curl_easy_setopt(curl, CURLOPT_WRITEDATA, leftover);
00453     res = curl_easy_perform(curl);
00454     curl_easy_cleanup(curl);
00455 
00456     if (res) {
00457         return 0;
00458     } else {
00459         return 1;
00460     }
00461 #else
00462     return 1;
00463 #endif
00464 }
00465 
00472 void *metaserver2_thread(void *junk)
00473 {
00474     int metaserver_choice, tries=0;
00475 
00476     do {
00477         metaserver_choice = random() % (sizeof(metaservers) / sizeof(char*));
00478         tries++;
00479         if (tries>5) {
00480             break;
00481         }
00482     } while (!get_metaserver2_data(metaservers[metaserver_choice]));
00483 
00484     pthread_mutex_lock(&ms2_info_mutex);
00485     qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort);
00486     ms2_is_running=0;
00487     pthread_mutex_unlock(&ms2_info_mutex);
00488     pthread_exit(NULL);
00489     // never reached, just to make the compiler happy.
00490     return NULL;
00491 }
00492 
00493 
00502 int metaserver2_get_info(void)
00503 {
00504     pthread_t   thread_id;
00505     int     ret;
00506 
00507     if (!metaserver2_on) {
00508         return 0;
00509     }
00510 #ifndef HAVE_CURL_CURL_H
00511     return 0;
00512 #endif
00513 
00514     metaserver_load_cache();
00515 
00516     pthread_mutex_lock(&ms2_info_mutex);
00517     if (!meta_servers) {
00518         meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info));
00519     }
00520 
00521     ms2_is_running=1;
00522     pthread_mutex_unlock(&ms2_info_mutex);
00523 
00524     ret=pthread_create(&thread_id, NULL, metaserver2_thread, NULL);
00525     if (ret) {
00526         LOG(LOG_ERROR, "common::metaserver2_get_info", "Thread creation failed.");
00527         pthread_mutex_lock(&ms2_info_mutex);
00528         ms2_is_running=0;
00529         pthread_mutex_unlock(&ms2_info_mutex);
00530     }
00531 
00532     return 0;
00533 }
00534 
00538 void init_metaserver(void)
00539 {
00540     pthread_mutex_init(&ms2_info_mutex, NULL);
00541 #ifdef HAVE_CURL_CURL_H
00542     curl_global_init(CURL_GLOBAL_ALL);
00543 #endif
00544 }
00545 
00546 /******************************************************************************
00547  * End of Metasever2 functions.
00548  ******************************************************************************/
00549 
00550 /******************************************************************************
00551  * Start of metaserver1 logic
00552  *
00553  * Note that this shares the same mutex as metaserver2, since it is updating
00554  * most of the same structures.
00555  *******************************************************************************/
00556 
00557 static int ms1_is_running=0;
00558 
00559 
00560 #ifdef WIN32
00561 /* Need script.h for script_killall */
00562 #include <script.h>
00563 
00568 char *get_line_from_sock(char *s, size_t n, int fd)
00569 {
00570     static long charsleft = 0;
00571     static char inbuf[MS_LARGE_BUF*4];
00572     char *cp;
00573     int ct;
00574 
00575     if (!s) {
00576         return s;
00577     }
00578     if (n != MS_LARGE_BUF*4-1) {
00579         LOG(LOG_CRITICAL, "common::get_line_from_sock", "Serious program logic error in get_line_from_sock().");
00580         exit(-1);
00581     }
00582 
00583     if (charsleft > MS_LARGE_BUF*4-3 && strchr(inbuf, '\n') == NULL) {
00584         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00585                       "Metaserver returned an overly long line.");
00586         return NULL;
00587     }
00588 
00589     /* If there is no line in the buffer */
00590     while (charsleft == 0 || (cp = strchr(inbuf, '\n')) == NULL) {
00591         FD_SET fdset;
00592         TIMEVAL tv = {3, 0}; /* 3 second timeout on reads */
00593         int nlen;
00594         FD_ZERO(&fdset);
00595         FD_SET(fd, &fdset);
00596         if (select(0, &fdset, NULL, NULL, &tv) == 0) {
00597             draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00598                           "Metaserver timed out.");
00599             return NULL;
00600         }
00601 
00602         nlen = recv(fd, inbuf+charsleft-1, MS_LARGE_BUF*4-1-charsleft, 0);
00603         if (nlen == SOCKET_ERROR || nlen <= 0) { /* Probably EOF */
00604             return NULL;
00605         }
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 
00709     /* The loop goes through and unpacks the data from the metaserver
00710      * into its individual components.  We do a little extra work and
00711      * put the |'s back in the string after we are done with that section -
00712      * this is so if there is a corrupt entry, it gets displayed as
00713      * originally received from the server.
00714      */
00715 #ifndef WIN32 /* Windows doesn't support this */
00716     while (fgets(inbuf, MS_LARGE_BUF*4-1, fp) != NULL) {
00717 #else
00718     while (get_line_from_sock(inbuf, MS_LARGE_BUF*4-1, fd) != NULL) {
00719 #endif
00720         char *cp, *cp1;
00721 
00722         cp = strchr(inbuf, '|');
00723         if (cp == NULL) {
00724             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00725             break;
00726         }
00727         *cp = 0;
00728 
00729         current = &meta_servers[meta_numservers];
00730 
00731         strncpy(current->ip_addr, inbuf, sizeof(current->ip_addr)-1);
00732         current->ip_addr[sizeof(current->ip_addr)-1] = '\0';
00733         *cp++ = '|';
00734 
00735         current->idle_time = atoi(cp);
00736 
00737         cp1 = strchr(cp, '|');
00738         if (cp1 == NULL) {
00739             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00740             break;
00741         }
00742         *cp1 = 0;
00743 
00744         cp = strchr(cp1+1, '|');
00745         if (cp == NULL) {
00746             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00747             break;
00748         }
00749         *cp = 0;
00750         /* cp1 points at start of comment, cp points at end */
00751         strncpy(current->hostname, cp1+1, sizeof(current->hostname)-1);
00752         current->hostname[sizeof(current->hostname)-1] = '\0';
00753 
00754         *cp1++ = '|';
00755         *cp++ = '|';  /* cp now points to num players */
00756 
00757         current->num_players = atoi(cp);
00758 
00759         cp1 = strchr(cp, '|');
00760         if (cp1 == NULL) {
00761             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00762             break;
00763         }
00764         *cp1 = 0;
00765 
00766         cp = strchr(cp1+1, '|');
00767         if (cp == NULL) {
00768             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00769             break;
00770         }
00771         *cp = 0;
00772         /* cp1 is start of version, cp is end */
00773         strncpy(current->version, cp1+1, sizeof(current->version)-1);
00774         current->version[sizeof(current->version)-1] = '\0';
00775 
00776         *cp1++ = '|';
00777         *cp++ = '|';  /* cp now points to comment */
00778 
00779         cp1 = strchr(cp, '\n');
00780         if (cp1 == NULL) {
00781             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00782             break;
00783         }
00784         *cp1 = 0;
00785         /* There is extra info included, like the bytes to/from the server
00786          * that we dont' care about, so strip them off so they don't show up in
00787          * the comment.
00788          */
00789         cp1 = strchr(cp, '|');
00790         if (cp1 != NULL) {
00791             *cp1 = 0;
00792         }
00793 
00794         strncpy(current->text_comment, cp, sizeof(current->text_comment)-1);
00795         current->text_comment[sizeof(current->text_comment)-1] = '\0';
00796 
00797         meta_numservers++;
00798         /* has to be 1 less than array size, since array starts counting
00799          * at 0.
00800          */
00801         if (meta_numservers >= MAX_METASERVER-1) {
00802             LOG(LOG_WARNING, "common:metaserver_get_info", "Have reached maximum metaserver count\n");
00803             break;
00804         }
00805     }
00806 #ifdef WIN32
00807     closesocket(fd);
00808 #else
00809     fclose(fp);
00810 #endif
00811     qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort);
00812     ms1_is_running=0;
00813     pthread_mutex_unlock(&ms2_info_mutex);
00814     pthread_exit(NULL);
00815     /* never reached, just to make the compiler happy. */
00816     return NULL;
00817 }
00818 
00822 int metaserver1_get_info(void)
00823 {
00824     pthread_t   thread_id;
00825     int     ret;
00826 
00827     if (!metaserver_on) {
00828         return 0;
00829     }
00830     metaserver_load_cache();
00831 
00832     pthread_mutex_lock(&ms2_info_mutex);
00833     if (!meta_servers) {
00834         meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info));
00835     }
00836 
00837     ms1_is_running=1;
00838     pthread_mutex_unlock(&ms2_info_mutex);
00839 
00840     ret=pthread_create(&thread_id, NULL, metaserver1_thread, NULL);
00841     if (ret) {
00842         LOG(LOG_ERROR, "common::metaserver1_get_info", "Thread creation failed.");
00843         pthread_mutex_lock(&ms2_info_mutex);
00844         ms1_is_running=0;
00845         pthread_mutex_unlock(&ms2_info_mutex);
00846     }
00847 
00848     return 0;
00849 }
00850 /******************************************************************************
00851  * End of metaserver1 logic
00852  ******************************************************************************/
00853 
00854 /******************************************************************************
00855  * This is start of common logic - the above sections are actually getting
00856  * the data.  The code below here is just displaying the data we got
00857  */
00858 
00869 int metaserver_check_status(void)
00870 {
00871     int status;
00872 
00873     pthread_mutex_lock(&ms2_info_mutex);
00874     status = ms2_is_running | ms1_is_running;
00875     pthread_mutex_unlock(&ms2_info_mutex);
00876 
00877     return status;
00878 }
00879 
00887 int metaserver_get_info(char *metaserver, int meta_port)
00888 {
00889 
00890     meta_numservers = 0;
00891 
00892     metaserver2_get_info();
00893 
00894     if (metaserver_on) {
00895         metaserver1_get_info();
00896     }
00897 
00898     return 0;
00899 }
00900 
00905 void metaserver_show(int show_selection)
00906 {
00907     int i;
00908     char buf[256];
00909 
00910     if (cached_servers_num) {
00911         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00912                       "\nLast servers you connected to:\n");
00913         for (i = 0; i < cached_servers_num; i++) {
00914             snprintf(buf, sizeof(buf), "%2d) %-20.20s %-20.20s", i+1, cached_servers_name[i], cached_servers_ip[i]);
00915             draw_ext_info(
00916                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf);
00917         }
00918         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, " ");
00919     }
00920 
00921     while(metaserver_check_status()) {
00922         usleep(100);
00923     }
00924 
00925     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00926                   " #)     Server        #     version   idle");
00927     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00928                   "         Name      players           seconds");
00929     pthread_mutex_lock(&ms2_info_mutex);
00930 
00931     /* Re-sort the data - may get different data from ms1 and ms2, so
00932      * order of this is somewhat random.
00933      */
00934     qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort);
00935     for (i = 0; i < meta_numservers; i++) {
00936         if (check_server_version(i)) {
00937             snprintf(buf, sizeof(buf), "%2d)  %-15.15s %2d   %-12.12s %2d",
00938                      i+1+cached_servers_num, meta_servers[i].hostname,
00939                      meta_servers[i].num_players, meta_servers[i].version,
00940                      meta_servers[i].idle_time);
00941             draw_ext_info(
00942                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf);
00943         }
00944     }
00945     if (show_selection) {
00946         /* Show default/current server */
00947         if (server) {
00948             snprintf(buf, sizeof(buf), "%2d)  %s (default)", meta_numservers+1+cached_servers_num, server);
00949             draw_ext_info(
00950                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf);
00951         }
00952 
00953         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00954                       "Choose one of the entries above");
00955         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00956                       "or type in a hostname/ip address");
00957         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00958                       "Hit enter to re-update this list");
00959         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00960                       "Enter 0 to exit the program.");
00961     }
00962     pthread_mutex_unlock(&ms2_info_mutex);
00963 }
00964 
00971 int metaserver_select(char *sel)
00972 {
00973     int num = atoi(sel);
00974     int port=0;
00975     char buf[MAX_BUF], buf2[MAX_BUF];
00976     char *server_name = NULL, *server_ip;
00977 
00978     /* User hit return */
00979     if (sel[0] == 0) {
00980         metaserver_get_info(meta_server, meta_port);
00981         metaserver_show(TRUE);
00982         return 1;
00983     }
00984 
00985     /* Special case - player really entered a 0, so exit the
00986      * program.
00987      */
00988     if (num == 0 && sel[0] == '0') {
00989 #ifdef WIN32
00990         script_killall();
00991 #endif
00992         exit(0);
00993     }
00994 
00995     pthread_mutex_lock(&ms2_info_mutex);
00996 
00997     /* if the entry is not a number (selection from the list),
00998      * or is a selection but also has a dot (suggesting
00999      * a.b.c.d selection), just try to connect with given name.
01000      */
01001     if (num == 0 || strchr(sel, '.') != NULL) {
01002         server_name = sel;
01003         server_ip = sel;
01004     } else {
01005         if (num <= 0 || num > meta_numservers+cached_servers_num+1) {
01006             draw_ext_info(
01007                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
01008                 "Invalid selection. Try again");
01009             return 1;
01010         }
01011 
01012         if (num == meta_numservers+cached_servers_num+1) {
01013             server_name = server;
01014             server_ip = server;
01015         } else if (num > cached_servers_num) {
01016             server_name = meta_servers[num-cached_servers_num-1 ].hostname;
01017             server_ip = meta_servers[num-cached_servers_num-1 ].ip_addr;
01018             port = meta_servers[num-cached_servers_num-1 ].port;
01019         } else {
01020             server_name = cached_servers_name[num-1];
01021             server_ip = cached_servers_ip[num-1];
01022         }
01023     }
01024     pthread_mutex_unlock(&ms2_info_mutex);
01025     if (!server_name) {
01026         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
01027                       "Bad selection. Try again");
01028         return 1;
01029     }
01030 
01031     /* check for :port suffix, and use it */
01032     if (!port) {
01033         if ((sel = strrchr(server_name, ':')) != NULL && (port = atoi(sel+1)) > 0) {
01034             snprintf(buf2, sizeof(buf2), "%s", server_name);
01035             buf2[sel-server_name] = '\0';
01036             server_name = buf2;
01037         } else {
01038             port = use_config[CONFIG_PORT];
01039         }
01040     }
01041 
01042     snprintf(buf, sizeof(buf), "Trying to connect to %s:%d", server_name, port);
01043     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER, buf);
01044     csocket.fd = init_connection(server_name, port);
01045     if (csocket.fd == -1) {
01046         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
01047                       "Unable to connect to server.");
01048         return 1;
01049     }
01050 
01051     /*
01052      * Upon successful connection, add the server to the cache or move it to
01053      * the top of the list.
01054      */
01055     if ((num <= meta_numservers)
01056             &&  (num != meta_numservers + cached_servers_num + 1)) {
01057         metaserver_update_cache(server_name, server_ip);
01058     }
01059 
01060     return 0;
01061 }
01062 
01063 #ifdef MS_STANDALONE
01064 /* This is here just to verify that the code seems to be working
01065  * properly, this tests both metaserver one and metaserver2
01066  * To use this code, compile as:
01067  *  gcc -o metaserver -I. -DMS_STANDALONE metaserver.c misc.o -lcurl -lpthread
01068  *  if you only want to have support for one type of server then use either
01069  *  -DMS_SA_NOTMS1 or -DMS_SA_NOTMS2 to disable the metaserver you don't want.
01070  *  The list of servers goes to stdout, the headers for the tables, status messages etc, go to stderr.
01071  */
01072 
01073 /* Following lines are to cover external symbols not
01074  * defined - trying to bring in the files the are defined
01075  * in just causes more dependencies, etc.
01076  */
01077 void draw_ext_info(int orig_color, int type, int subtype, const char *message) {}
01078 int init_connection(char *host, int port) {}
01079 
01080 int metaserver2_on=1, metaserver_on=1;
01081 char *server=NULL;
01082 sint16 use_config[CONFIG_NUMS];
01083 ClientSocket csocket;
01084 char *meta_server=META_SERVER;
01085 int meta_port=META_PORT;
01086 
01090 void handle_ms_data(int msservernum)
01091 {
01092     int i;
01093     fprintf(stderr,"Collecting data from metaserver %d.", msservernum);
01094     while (metaserver_check_status()) {
01095         fprintf(stderr,".");
01096         sleep(1);
01097     }
01098     fprintf(stderr, "\nIp Address:Idle Time:Hostname:Players:Version:Comment\n");
01099     for (i = 0; i < meta_numservers; i++) {
01100         printf("%s:%d:%s:%d:%s:%s\n",
01101                meta_servers[i].ip_addr,
01102                meta_servers[i].idle_time,
01103                meta_servers[i].hostname,
01104                meta_servers[i].num_players,
01105                meta_servers[i].version,
01106                meta_servers[i].text_comment);
01107     }
01108     fprintf(stderr, "%d servers found\n", meta_numservers);
01109 }
01110 
01114 int main(int argc, char *argv[])
01115 {
01116 
01117 #ifdef MS_SA_NOTMS2
01118     metaserver2_on=0;
01119 #endif
01120 #ifdef MS_SA_NOTMS1
01121     metaserver_on=0;
01122 #endif
01123 
01124     init_metaserver();
01125     if(metaserver2_on) {
01126         metaserver2_get_info();
01127         handle_ms_data(2);
01128     }
01129     /* both metaservers use the same array to store the servers in, so we'll
01130      * reset it here in order to get the results from the other metaserver. */
01131     free(meta_servers);
01132     meta_servers=NULL;
01133     meta_numservers = 0;
01134     if(metaserver_on) {
01135         metaserver1_get_info();
01136         handle_ms_data(1);
01137     }
01138 }
01139 
01140 #endif