Crossfire Client, Branch  R11627
metaserver.c
Go to the documentation of this file.
00001 const char * const rcsid_common_metaserver_c =
00002     "$Id: metaserver.c 11626 2009-04-04 12:41:46Z lalo $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2001 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 
00025 /* This file deals with contact the metaserver, getting a list of hosts,
00026  * displaying/returning them to calling function, and then connecting
00027  * to the server when requested.
00028  */
00029 
00030 #ifndef WIN32
00031 #include <netdb.h>
00032 #include <sys/types.h>
00033 #include <sys/socket.h>
00034 #include <netinet/in.h>
00035 #include <arpa/inet.h>
00036 #endif /* WIN32 */
00037 
00038 #include <ctype.h>
00039 #include <stdio.h>
00040 #include <stdlib.h>
00041 
00042 #include <client.h>
00043 #include <cconfig.h>
00044 #include <external.h>
00045 
00046 #include <metaserver.h>
00047 
00048 #ifdef HAVE_CURL_CURL_H
00049 #include <curl/curl.h>
00050 #include <curl/types.h>
00051 #include <curl/easy.h>
00052 #endif
00053 
00054 
00055 Meta_Info *meta_servers = NULL;
00056 
00057 int meta_numservers = 0;
00058 
00059 /* This checks the servers sc_version and cs_version to see
00060  * if they are compatible.
00061  * @parm entry
00062  * entry number in the metaservers array to check.
00063  * @return
00064  * 1 if this entry is compatible, 0 if it is not.  Note that this can
00065  * only meaningfully check metaserver2 data - metaserver1 doesn't
00066  * include protocol version number, so treats all of those as
00067  * OK.
00068  */
00069 int check_server_version(int entry)
00070 {
00071 
00072     /* No version information - nothing to do. */
00073     if (!meta_servers[entry].sc_version || !meta_servers[entry].cs_version)
00074         return 1;
00075 
00076     if (meta_servers[entry].sc_version != VERSION_SC) {
00077         /* 1027->1028 removed a bunch of old commands, so a 1028
00078          * version client can still play on a 1027 server, so
00079          * special hard code that.
00080          *
00081          * Likewise, 1028->1029 just changed how weapon_speed
00082          * should be interperted on the client - the client
00083          * does the right thing, so not problem with a 1029
00084          * client playing on 1028 or 1027 server.
00085          *
00086          * A 1028 client could in practice play on a 1029
00087          * server, since at the protocol level, data is the same -
00088          * the client would just have screwed up weapon_sp values.
00089          */
00090         if ((VERSION_SC == 1028 || VERSION_SC==1029) &&
00091             (meta_servers[entry].sc_version==1027 ||
00092              meta_servers[entry].sc_version==1028))
00093             return 1;
00094     }
00095     if (meta_servers[entry].cs_version != VERSION_CS) return 0;
00096 
00097     return 1;
00098 }
00099 
00100 /*****************************************************************************
00101  * Start of cache related functions.
00102  *****************************************************************************/
00103 int cached_servers_num = 0;
00104 char *cached_servers_name[CACHED_SERVERS_MAX];
00105 char *cached_servers_ip[CACHED_SERVERS_MAX];
00106 static int cached_servers_loaded = 0;
00107 const char *cached_server_file = NULL;
00108 
00109 
00110 static void metaserver_load_cache(void) {
00111     FILE *cache;
00112     char buf[ MS_LARGE_BUF ];
00113     int name;
00114 
00115     if (cached_servers_loaded || !cached_server_file)
00116         return;
00117 
00118     /* If failure, we don't want to load again */
00119     cached_servers_loaded = 1;
00120     cached_servers_num = 0;
00121 
00122     cache = fopen(cached_server_file, "r");
00123     if (!cache)
00124         return;
00125 
00126     name = 0;
00127     while (fgets(buf, MS_LARGE_BUF, cache) != NULL && cached_servers_num < CACHED_SERVERS_MAX) {
00128         buf[strlen(buf)-1] = 0;
00129         if (!name) {
00130             name = 1;
00131             cached_servers_name[cached_servers_num] = strdup(buf);
00132         } else {
00133             name = 0;
00134             cached_servers_ip[cached_servers_num++] = strdup(buf);
00135         }
00136     }
00137     fclose(cache);
00138     if (name) {
00139         /* Missing IP? */
00140         cached_servers_num--;
00141     }
00142 }
00143 
00144 static void metaserver_save_cache(void) {
00145     FILE *cache;
00146     int server;
00147 
00148     if (!cached_server_file)
00149         return;
00150 
00151     cache = fopen(cached_server_file, "w");
00152     if (!cache)
00153         return;
00154 
00155     for (server = 0; server < cached_servers_num; server++) {
00156         fprintf(cache, "%s\n", cached_servers_name[server]);
00157         fprintf(cache, "%s\n", cached_servers_ip[server]);
00158     }
00159     fclose(cache);
00160 }
00161 
00162 /*****************************************************************************
00163  * End of cache related functions.
00164  *****************************************************************************/
00165 
00166 /******************************************************************************
00167  * Metaserver2 support starts here.
00168  *
00169  ******************************************************************************/
00170 
00171 pthread_mutex_t ms2_info_mutex;
00172 
00173 /* we use threads so that the GUI keeps responding while we wait for
00174  * data.  But we need to note if the thread is running or not,
00175  * so we store it here.  This, like the other metaserver2 data,
00176  * should be protected by using the ms2_info_mutext.
00177  */
00178 static int ms2_is_running=0;
00179 
00180 /* list of metaserver URL to get information from - this should generally
00181  * correspond to the value in the metaserver2 server file, but instead
00182  * of meta_update.php, use meta_client.php.
00183  *
00184  * These could perhaps be in some other file (config.h or the like), but
00185  * it seems unlikely that these will change very often, and certainly not
00186  * at a level where we would expect users to go about changing the values.
00187  */
00188 static char *metaservers[] = {"http://crossfire.real-time.com/metaserver2/meta_client.php"};
00189 
00214 size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data)
00215 {
00216 #ifdef HAVE_CURL_CURL_H
00217     size_t realsize = size * nmemb;
00218     char    *cp, *newline, *eq, inbuf[CURL_MAX_WRITE_SIZE*2+1], *leftover;
00219 
00220     leftover = (char*) data;
00221 
00222     if (realsize > CURL_MAX_WRITE_SIZE) {
00223         LOG(LOG_CRITICAL, "common::metaserver2_writer", "Function called with more data than allowed!");
00224     }
00225 
00226     /* This memcpy here is to just give us a null terminated character
00227      * array - easier to do with than having to check lengths as well as other
00228      * values.  Also, it makes it easier to deal with unprocessed data from
00229      * the last call.
00230      */
00231     memcpy(inbuf, leftover, strlen(leftover));
00232     memcpy(inbuf+strlen(leftover), ptr, realsize);
00233     inbuf[realsize] = 0;
00234 
00235     /* Processing this block of data shouldn't take very long, even on
00236      * slow machines, so putting the lock here, instead of each time
00237      * we update a variable is cleaner
00238      */
00239     pthread_mutex_lock(&ms2_info_mutex);
00240 
00241     for (cp = inbuf; cp != NULL && *cp!=0; cp=newline) {
00242         newline=strchr(cp, '\n');
00243         if (newline) {
00244             *newline = 0;
00245             newline++;
00246         } else {
00247             /* If we didn't get a newline, then this is the
00248              * end of the block of data for this call - store
00249              * away the extra for the next call.
00250              */
00251             strncpy(leftover, cp, CURL_MAX_WRITE_SIZE-1);
00252             leftover[CURL_MAX_WRITE_SIZE-1] = 0;
00253             break;
00254         }
00255 
00256         eq = strchr(cp,'=');
00257         if (eq) {
00258             *eq = 0;
00259             eq++;
00260         }
00261 
00262         if (!strcmp(cp, "START_SERVER_DATA")) {
00263             /* Clear out all data - MS2 doesn't necessarily use all the
00264              * fields, so blank out any that we are not using.
00265              */
00266             memset(&meta_servers[meta_numservers], 0, sizeof(Meta_Info));
00267         }
00268         else if (!strcmp(cp, "END_SERVER_DATA")) {
00269             int i;
00270 
00271             /* we can get data from both metaserver1 & 2 - no reason to keep
00272              * both.  So check for duplicates, and consider metaserver2
00273              * data 'better'.
00274              */
00275             for (i=0; i<meta_numservers; i++) {
00276                 if (!strcasecmp(meta_servers[i].hostname, meta_servers[meta_numservers].hostname)) {
00277                     memcpy(&meta_servers[i], &meta_servers[meta_numservers], sizeof(Meta_Info));
00278                     break;
00279                 }
00280             }
00281             if (i>=meta_numservers) {
00282                 meta_numservers++;
00283             }
00284         } else {
00285             /* If we get here, these should be variable=value pairs.
00286              * if we don't have a value, can't do anything, and
00287              * report an error.  This would normally be incorrect
00288              * data from the server.
00289              */
00290             if (!eq) {
00291                 LOG(LOG_ERROR, "common::metaserver2_writer", "Unknown line: %s",cp);
00292                 continue;
00293             }
00294             if (!strcmp(cp,"hostname")) {
00295                 strncpy(meta_servers[meta_numservers].hostname, eq, sizeof(meta_servers[meta_numservers].hostname));
00296             }
00297             else if (!strcmp(cp,"port")) {
00298                 meta_servers[meta_numservers].port = atoi(eq);
00299             }
00300             else if (!strcmp(cp,"html_comment")) {
00301                 strncpy(meta_servers[meta_numservers].html_comment, eq, sizeof(meta_servers[meta_numservers].html_comment));
00302             }
00303             else if (!strcmp(cp,"text_comment")) {
00304                 strncpy(meta_servers[meta_numservers].text_comment, eq, sizeof(meta_servers[meta_numservers].text_comment));
00305             }
00306             else if (!strcmp(cp,"archbase")) {
00307                 strncpy(meta_servers[meta_numservers].archbase, eq, sizeof(meta_servers[meta_numservers].archbase));
00308             }
00309             else if (!strcmp(cp,"mapbase")) {
00310                 strncpy(meta_servers[meta_numservers].mapbase, eq, sizeof(meta_servers[meta_numservers].mapbase));
00311             }
00312             else if (!strcmp(cp,"codebase")) {
00313                 strncpy(meta_servers[meta_numservers].codebase, eq, sizeof(meta_servers[meta_numservers].codebase));
00314             }
00315             else if (!strcmp(cp,"flags")) {
00316                 strncpy(meta_servers[meta_numservers].flags, eq, sizeof(meta_servers[meta_numservers].flags));
00317             }
00318             else if (!strcmp(cp,"version")) {
00319                 strncpy(meta_servers[meta_numservers].version, eq, sizeof(meta_servers[meta_numservers].version));
00320             }
00321             else if (!strcmp(cp,"num_players")) {
00322                 meta_servers[meta_numservers].num_players = atoi(eq);
00323             }
00324             else if (!strcmp(cp,"in_bytes")) {
00325                 meta_servers[meta_numservers].in_bytes = atoi(eq);
00326             }
00327             else if (!strcmp(cp,"out_bytes")) {
00328                 meta_servers[meta_numservers].out_bytes = atoi(eq);
00329             }
00330             else if (!strcmp(cp,"uptime")) {
00331                 meta_servers[meta_numservers].uptime = atoi(eq);
00332             }
00333             else if (!strcmp(cp,"sc_version")) {
00334                 meta_servers[meta_numservers].sc_version = atoi(eq);
00335             }
00336             else if (!strcmp(cp,"cs_version")) {
00337                 meta_servers[meta_numservers].cs_version = atoi(eq);
00338             }
00339             else if (!strcmp(cp,"last_update")) {
00340                 /* MS2 reports update time as when it last got an update,
00341                  * where as we want actual elapsed time since last update.
00342                  * So do the conversion.  Second check is because of clock
00343                  * skew - my clock may be fast, and we don't want negative times.
00344                  */
00345                 meta_servers[meta_numservers].idle_time = time(NULL) - atoi(eq);
00346                 if (meta_servers[meta_numservers].idle_time < 0)
00347                      meta_servers[meta_numservers].idle_time = 0;
00348             }
00349             else {
00350                 LOG(LOG_ERROR, "common::metaserver2_writer", "Unknown line: %s=%s",cp,eq);
00351             }
00352         }
00353     }
00354     pthread_mutex_unlock(&ms2_info_mutex);
00355     return realsize;
00356 #else
00357     return 0;
00358 #endif
00359 }
00360 
00369 static int get_metaserver2_data(char *metaserver2) {
00370 #ifdef HAVE_CURL_CURL_H
00371     CURL *curl;
00372     CURLcode res;
00373     char    leftover[CURL_MAX_WRITE_SIZE];
00374 
00375     curl = curl_easy_init();
00376     if (!curl) return 0;
00377     leftover[0] =0;
00378     curl_easy_setopt(curl, CURLOPT_URL, metaserver2);
00379     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer);
00380     curl_easy_setopt(curl, CURLOPT_WRITEDATA, leftover);
00381     res = curl_easy_perform(curl);
00382     curl_easy_cleanup(curl);
00383 
00384     if (res) return 0;
00385     else return 1;
00386 #else
00387     return 1;
00388 #endif
00389 }
00390 
00397 void *metaserver2_thread(void *junk)
00398 {
00399     int metaserver_choice;
00400 
00401     do {
00402         metaserver_choice = random() % (sizeof(metaservers) / sizeof(char*));
00403     } while (!get_metaserver2_data(metaservers[metaserver_choice]));
00404 
00405     pthread_mutex_lock(&ms2_info_mutex);
00406     qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort);
00407     ms2_is_running=0;
00408     pthread_mutex_unlock(&ms2_info_mutex);
00409     pthread_exit(NULL);
00410     // never reached, just to make the compiler happy.
00411     return NULL;
00412 }
00413 
00414 
00423 int metaserver2_get_info(void) {
00424     pthread_t   thread_id;
00425     int     ret;
00426 
00427     if (!metaserver2_on) {
00428         return 0;
00429     }
00430 #ifndef HAVE_CURL_CURL_H
00431     return 0;
00432 #endif
00433 
00434     metaserver_load_cache();
00435 
00436     pthread_mutex_lock(&ms2_info_mutex);
00437     if (!meta_servers)
00438         meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info));
00439 
00440     ms2_is_running=1;
00441     pthread_mutex_unlock(&ms2_info_mutex);
00442 
00443     ret=pthread_create(&thread_id, NULL, metaserver2_thread, NULL);
00444     if (ret) {
00445         LOG(LOG_ERROR, "common::metaserver2_get_info", "Thread creation failed.");
00446         pthread_mutex_lock(&ms2_info_mutex);
00447         ms2_is_running=0;
00448         pthread_mutex_unlock(&ms2_info_mutex);
00449     }
00450 
00451     return 0;
00452 }
00453 
00454 
00455 
00459 void init_metaserver(void)
00460 {
00461     pthread_mutex_init(&ms2_info_mutex, NULL);
00462 #ifdef HAVE_CURL_CURL_H
00463     curl_global_init(CURL_GLOBAL_ALL);
00464 #endif
00465 }
00466 
00467 /******************************************************************************
00468  * End of Metasever2 functions.
00469  ******************************************************************************/
00470 
00471 /******************************************************************************
00472  * Start of metaserver1 logic
00473  *
00474  * Note that this shares the same mutex as metaserver2, since it is updating
00475  * most of the same structures.
00476  *******************************************************************************/
00477 
00478 static int ms1_is_running=0;
00479 
00480 
00481 #ifdef WIN32
00482 /* Need script.h for script_killall */
00483 #include <script.h>
00484 
00485 /* This gets input from a socket, and returns it one line at a time.
00486  */
00487 /* This is a Windows-specific function, since you can't use fgets under Win32 */
00488 char *get_line_from_sock(char *s, size_t n, int fd) {
00489     static long charsleft = 0;
00490     static char inbuf[MS_LARGE_BUF*4];
00491     char *cp;
00492     int ct;
00493 
00494     if (!s)
00495         return s;
00496     if (n != MS_LARGE_BUF*4-1) {
00497         LOG(LOG_CRITICAL, "common::get_line_from_sock", "Serious program logic error in get_line_from_sock().");
00498         exit(-1);
00499     }
00500 
00501     if (charsleft > MS_LARGE_BUF*4-3 && strchr(inbuf, '\n') == NULL) {
00502         draw_info("Metaserver returned an overly long line.", NDI_BLACK);
00503         return NULL;
00504     }
00505 
00506     /* If there is no line in the buffer */
00507     while (charsleft == 0 || (cp = strchr(inbuf, '\n')) == NULL) {
00508         FD_SET fdset;
00509         TIMEVAL tv = {3, 0}; /* 3 second timeout on reads */
00510         int nlen;
00511         FD_ZERO(&fdset);
00512         FD_SET(fd, &fdset);
00513         if (select(0, &fdset, NULL, NULL, &tv) == 0) {
00514             draw_info("Metaserver timed out.", NDI_BLACK);
00515             return NULL;
00516         }
00517 
00518         nlen = recv(fd, inbuf+charsleft-1, MS_LARGE_BUF*4-1-charsleft, 0);
00519         if (nlen == SOCKET_ERROR || nlen <= 0) /* Probably EOF */
00520             return NULL;
00521 
00522         charsleft += nlen;
00523     }
00524 
00525     /* OK, inbuf contains a null terminated string with at least one \n
00526      * Copy the string up to the \n to s, and then move the rest of the
00527      * inbuf string to the beginning of the buffer.  And finally, set
00528      * charsleft to the number of characters left in inbuf, or 0.
00529      * Oh, and cp contains the location of the \n.
00530      */
00531 
00532     memcpy(s, inbuf, cp-inbuf+1); /* Extract the line, including the \n. */
00533     s[cp-inbuf+1] = 0; /* null terminate it */
00534 
00535     /* Copy cp to inbuf up to the \0, (skipping the \n) */
00536     ct = 0;
00537     while (cp[++ct] != 0) {
00538         inbuf[ct-1] = cp[ct];
00539     }
00540     inbuf[ct-1] = 0;
00541     charsleft = ct;    /* And keep track of how many characters are left. */
00542 
00543     return s;
00544 }
00545 
00546 #endif /* Win32 */
00547 
00548 void *metaserver1_thread(void *junk)
00549 {
00550     struct protoent *protox;
00551     int fd;
00552     struct sockaddr_in insock;
00553 #ifndef WIN32
00554     FILE *fp;
00555 #endif
00556     char inbuf[MS_LARGE_BUF*4];
00557     Meta_Info *current;
00558 
00559     protox = getprotobyname("tcp");
00560     if (protox == NULL) {
00561         LOG(LOG_WARNING, "common::metaserver_get_info", "Error getting protobyname (tcp)");
00562         pthread_mutex_lock(&ms2_info_mutex);
00563         ms1_is_running=0;
00564         pthread_mutex_unlock(&ms2_info_mutex);
00565         pthread_exit(NULL);
00566     }
00567 
00568     fd = socket(PF_INET, SOCK_STREAM, protox->p_proto);
00569     if (fd == -1) {
00570         perror("get_metaserver_info:  Error on socket command.\n");
00571         pthread_mutex_lock(&ms2_info_mutex);
00572         ms1_is_running=0;
00573         pthread_mutex_unlock(&ms2_info_mutex);
00574         pthread_exit(NULL);
00575     }
00576     insock.sin_family = AF_INET;
00577     insock.sin_port = htons((unsigned short)meta_port);
00578     if (isdigit(*meta_server))
00579         insock.sin_addr.s_addr = inet_addr(meta_server);
00580     else {
00581         struct hostent *hostbn = gethostbyname(meta_server);
00582         if (hostbn == NULL) {
00583             LOG(LOG_WARNING, "common::metaserver_get_info", "Unknown metaserver hostname: %s", meta_server);
00584             pthread_mutex_lock(&ms2_info_mutex);
00585             ms1_is_running=0;
00586             pthread_mutex_unlock(&ms2_info_mutex);
00587             pthread_exit(NULL);
00588         }
00589         memcpy(&insock.sin_addr, hostbn->h_addr, hostbn->h_length);
00590     }
00591     if (connect(fd, (struct sockaddr *)&insock, sizeof(insock)) == -1) {
00592         perror("Can't connect to metaserver");
00593         draw_info("\nCan't connect to metaserver.", NDI_BLACK);
00594         pthread_mutex_lock(&ms2_info_mutex);
00595         ms1_is_running=0;
00596         pthread_mutex_unlock(&ms2_info_mutex);
00597         pthread_exit(NULL);
00598     }
00599 
00600 #ifndef WIN32 /* Windows doesn't support this */
00601     /* Turn this into a file handle - this will break it on newlines
00602      * for us, which makes our processing much easier - it basically
00603      * means one line/server
00604      */
00605     if ((fp = fdopen(fd, "r")) == NULL) {
00606         perror("fdopen failed.");
00607         pthread_mutex_lock(&ms2_info_mutex);
00608         ms1_is_running=0;
00609         pthread_mutex_unlock(&ms2_info_mutex);
00610         pthread_exit(NULL);
00611     }
00612 #endif
00613 
00614     pthread_mutex_lock(&ms2_info_mutex);
00615     if (!meta_servers)
00616         meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info));
00617 
00618 
00619     /* The loop goes through and unpacks the data from the metaserver
00620      * into its individual components.  We do a little extra work and
00621      * put the |'s back in the string after we are done with that section -
00622      * this is so if there is a corrupt entry, it gets displayed as
00623      * originally received from the server.
00624      */
00625 #ifndef WIN32 /* Windows doesn't support this */
00626     while (fgets(inbuf, MS_LARGE_BUF*4-1, fp) != NULL) {
00627 #else
00628     while (get_line_from_sock(inbuf, MS_LARGE_BUF*4-1, fd) != NULL) {
00629 #endif
00630         char *cp, *cp1;
00631 
00632         cp = strchr(inbuf, '|');
00633         if (cp == NULL) {
00634             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00635             break;
00636         }
00637         *cp = 0;
00638 
00639         current = &meta_servers[meta_numservers];
00640 
00641         strncpy(current->ip_addr, inbuf, sizeof(current->ip_addr)-1);
00642         current->ip_addr[sizeof(current->ip_addr)-1] = '\0';
00643         *cp++ = '|';
00644 
00645         current->idle_time = atoi(cp);
00646 
00647         cp1 = strchr(cp, '|');
00648         if (cp1 == NULL) {
00649             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00650             break;
00651         }
00652         *cp1 = 0;
00653 
00654         cp = strchr(cp1+1, '|');
00655         if (cp == NULL) {
00656             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00657             break;
00658         }
00659         *cp = 0;
00660         /* cp1 points at start of comment, cp points at end */
00661         strncpy(current->hostname, cp1+1, sizeof(current->hostname)-1);
00662         current->hostname[sizeof(current->hostname)-1] = '\0';
00663 
00664         *cp1++ = '|';
00665         *cp++ = '|';  /* cp now points to num players */
00666 
00667         current->num_players = atoi(cp);
00668 
00669         cp1 = strchr(cp, '|');
00670         if (cp1 == NULL) {
00671             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00672             break;
00673         }
00674         *cp1 = 0;
00675 
00676         cp = strchr(cp1+1, '|');
00677         if (cp == NULL) {
00678             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00679             break;
00680         }
00681         *cp = 0;
00682         /* cp1 is start of version, cp is end */
00683         strncpy(current->version, cp1+1, sizeof(current->version)-1);
00684         current->version[sizeof(current->version)-1] = '\0';
00685 
00686         *cp1++ = '|';
00687         *cp++ = '|';  /* cp now points to comment */
00688 
00689         cp1 = strchr(cp, '\n');
00690         if (cp1 == NULL) {
00691             LOG(LOG_WARNING, "common::metaserver_get_info", "Corrupt line from server: %s", inbuf);
00692             break;
00693         }
00694         *cp1 = 0;
00695         /* There is extra info included, like the bytes to/from the server
00696          * that we dont' care about, so strip them off so they don't show up in
00697          * the comment.
00698          */
00699         cp1 = strchr(cp, '|');
00700         if (cp1 != NULL)
00701             *cp1 = 0;
00702 
00703         strncpy(current->text_comment, cp, sizeof(current->text_comment)-1);
00704         current->text_comment[sizeof(current->text_comment)-1] = '\0';
00705 
00706         meta_numservers++;
00707         /* has to be 1 less than array size, since array starts counting
00708          * at 0.
00709          */
00710         if (meta_numservers >= MAX_METASERVER-1) {
00711             LOG(LOG_WARNING, "common:metaserver_get_info", "Have reached maximum metaserver count\n");
00712             break;
00713         }
00714     }
00715 #ifdef WIN32
00716     closesocket(fd);
00717 #else
00718     fclose(fp);
00719 #endif
00720     qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort);
00721     ms1_is_running=0;
00722     pthread_mutex_unlock(&ms2_info_mutex);
00723     pthread_exit(NULL);
00724     // never reached, just to make the compiler happy.
00725     return NULL;
00726 }
00727 
00728 
00729 int metaserver1_get_info(void) {
00730     pthread_t   thread_id;
00731     int     ret;
00732 
00733     if (!metaserver_on) {
00734         return 0;
00735     }
00736     metaserver_load_cache();
00737 
00738     pthread_mutex_lock(&ms2_info_mutex);
00739     if (!meta_servers)
00740         meta_servers = calloc(MAX_METASERVER, sizeof(Meta_Info));
00741 
00742     ms1_is_running=1;
00743     pthread_mutex_unlock(&ms2_info_mutex);
00744 
00745     ret=pthread_create(&thread_id, NULL, metaserver1_thread, NULL);
00746     if (ret) {
00747         LOG(LOG_ERROR, "common::metaserver1_get_info", "Thread creation failed.");
00748         pthread_mutex_lock(&ms2_info_mutex);
00749         ms1_is_running=0;
00750         pthread_mutex_unlock(&ms2_info_mutex);
00751     }
00752 
00753     return 0;
00754 }
00755 /******************************************************************************
00756  * End of metaserver1 logic
00757  ******************************************************************************/
00758 
00759 /******************************************************************************
00760  * This is start of common logic - the above sections are actually getting
00761  * the data.  The code below here is just displaying the data we got
00762  */
00763 
00774 int metaserver_check_status(void) {
00775     int status;
00776 
00777     pthread_mutex_lock(&ms2_info_mutex);
00778     status = ms2_is_running | ms1_is_running;
00779     pthread_mutex_unlock(&ms2_info_mutex);
00780 
00781     return status;
00782 }
00783 
00784 /* This contacts the metaserver and gets the list of servers.  returns 0
00785  * on success, 1 on failure.  Errors will get dumped to stderr,
00786  * so most errors should be reasonably clear.
00787  * metaserver and meta_port are the server name and port number
00788  * to connect to.
00789  */
00790 
00791 int metaserver_get_info(char *metaserver, int meta_port) {
00792 
00793     meta_numservers = 0;
00794 
00795     metaserver2_get_info();
00796 
00797     if (metaserver_on) {
00798         metaserver1_get_info();
00799     }
00800 
00801     return 0;
00802 }
00803 
00804 /* show the metaservers to the player.  we use the draw_info to do
00805  * that, and also let the player know they can enter their own host name.
00806  */
00807 void metaserver_show(int show_selection) {
00808     int i;
00809     char buf[256];
00810 
00811     if (cached_servers_num) {
00812         draw_info("\nLast servers you connected to:\n", NDI_BLACK);
00813         for (i = 0; i < cached_servers_num; i++) {
00814             snprintf(buf, sizeof(buf), "%2d) %-20.20s %-20.20s", i+1, cached_servers_name[i], cached_servers_ip[i]);
00815             draw_info(buf, NDI_BLACK);
00816         }
00817         draw_info(" ", NDI_BLACK);
00818     }
00819 
00820     while(metaserver_check_status()) {
00821         usleep(100);
00822     }
00823 
00824     draw_info(" #)     Server        #     version   idle", NDI_BLACK);
00825     draw_info("         Name      players           seconds", NDI_BLACK);
00826     pthread_mutex_lock(&ms2_info_mutex);
00827 
00828     /* Re-sort the data - may get different data from ms1 and ms2, so
00829      * order of this is somewhat random.
00830      */
00831     qsort(meta_servers, meta_numservers, sizeof(Meta_Info), (int (*)(const void *, const void *))meta_sort);
00832     for (i = 0; i < meta_numservers; i++) {
00833         if (check_server_version(i)) {
00834             snprintf(buf, sizeof(buf), "%2d)  %-15.15s %2d   %-12.12s %2d",
00835                     i+1+cached_servers_num, meta_servers[i].hostname,
00836                     meta_servers[i].num_players, meta_servers[i].version,
00837                     meta_servers[i].idle_time);
00838             draw_info(buf, NDI_BLACK);
00839         }
00840     }
00841     if (show_selection) {
00842         /* Show default/current server */
00843         if (server) {
00844             snprintf(buf, sizeof(buf), "%2d)  %s (default)", meta_numservers+1+cached_servers_num, server);
00845             draw_info(buf, NDI_BLACK);
00846         }
00847 
00848         draw_info("Choose one of the entries above", NDI_BLACK);
00849         draw_info("or type in a hostname/ip address", NDI_BLACK);
00850         draw_info("Hit enter to re-update this list", NDI_BLACK);
00851         draw_info("Enter 0 to exit the program.", NDI_BLACK);
00852     }
00853     pthread_mutex_unlock(&ms2_info_mutex);
00854 }
00855 
00856 /* String contains the selection that the player made for the metaserver.
00857  * this may not be a a selection, but could be a host name or ip address.
00858  * this returns 0 on sucessful selection, 1 if failure (invalid selection
00859  * or the like.
00860  */
00861 int metaserver_select(char *sel) {
00862     int num = atoi(sel);
00863     int port=0;
00864     char buf[MAX_BUF], buf2[MAX_BUF];
00865     char *server_name = NULL, *server_ip;
00866 
00867     /* User hit return */
00868     if (sel[0] == 0) {
00869         metaserver_get_info(meta_server, meta_port);
00870         metaserver_show(TRUE);
00871         return 1;
00872     }
00873 
00874     /* Special case - player really entered a 0, so exit the
00875      * program.
00876      */
00877     if (num == 0 && sel[0] == '0') {
00878 #ifdef WIN32
00879         script_killall();
00880 #endif
00881         exit(0);
00882     }
00883 
00884     pthread_mutex_lock(&ms2_info_mutex);
00885 
00886     /* if the entry is not a number (selection from the list),
00887      * or is a selection but also has a dot (suggesting
00888      * a.b.c.d selection), just try to connect with given name.
00889      */
00890     if (num == 0 || strchr(sel, '.') != NULL) {
00891         server_name = sel;
00892         server_ip = sel;
00893     } else {
00894         if (num <= 0 || num > meta_numservers+cached_servers_num+1) {
00895             draw_info("Invalid selection. Try again", NDI_BLACK);
00896             return 1;
00897         }
00898 
00899         if (num == meta_numservers+cached_servers_num+1) {
00900             server_name = server;
00901             server_ip = server;
00902         } else if (num > cached_servers_num) {
00903             server_name = meta_servers[num-cached_servers_num-1 ].hostname;
00904             server_ip = meta_servers[num-cached_servers_num-1 ].ip_addr;
00905             port = meta_servers[num-cached_servers_num-1 ].port;
00906         } else {
00907             server_name = cached_servers_name[num-1];
00908             server_ip = cached_servers_ip[num-1];
00909         }
00910     }
00911     pthread_mutex_unlock(&ms2_info_mutex);
00912     if (!server_name) {
00913         draw_info("Bad selection. Try again", NDI_BLACK);
00914         return 1;
00915     }
00916 
00917     /* check for :port suffix, and use it */
00918     if (!port) {
00919         if ((sel = strrchr(server_name, ':')) != NULL && (port = atoi(sel+1)) > 0) {
00920             snprintf(buf2, sizeof(buf2), "%s", server_name);
00921             buf2[sel-server_name] = '\0';
00922             server_name = buf2;
00923         }
00924         else {
00925             port = use_config[CONFIG_PORT];
00926         }
00927     }
00928 
00929     snprintf(buf, sizeof(buf), "Trying to connect to %s:%d", server_name, port);
00930     draw_info(buf, NDI_BLACK);
00931 #ifdef MULTKEYS
00932     csocket.fd = init_connection(server_name, port);
00933 #else
00934     csocket.fd = init_connection(server_ip, port);
00935 #endif
00936     if (csocket.fd == -1) {
00937         draw_info("Unable to connect to server.", NDI_BLACK);
00938         return 1;
00939     }
00940 
00941     /* Add server to cache */
00942     if ((num <= meta_numservers) && (num != meta_numservers + cached_servers_num + 1)) {
00943         int index;
00944         for (index = 0; index < cached_servers_num; index++) {
00945             if (strcmp(server_name, cached_servers_name[index]) == 0)
00946                 break;
00947         }
00948         /* If server is first in cache, no need to re-add id */
00949         if (index != 0 || !cached_servers_num) {
00950             char *name;
00951             char *ip;
00952             int copy;
00953 
00954             if (index == cached_servers_num) {
00955                 name = strdup(server_name);
00956                 ip = strdup(server_ip);
00957                 cached_servers_num++;
00958                 if (cached_servers_num > CACHED_SERVERS_MAX) {
00959                     cached_servers_num--;
00960                     free(cached_servers_name[cached_servers_num-1]);
00961                     free(cached_servers_ip[cached_servers_num-1]);
00962                 }
00963             } else {
00964                 name = cached_servers_name[index];
00965                 ip = cached_servers_ip[index];
00966             }
00967             for (copy = MIN(index, CACHED_SERVERS_MAX-1); copy > 0; copy--) {
00968                 cached_servers_name[copy] = cached_servers_name[copy-1];
00969                 cached_servers_ip[copy] = cached_servers_ip[copy-1];
00970             }
00971             cached_servers_name[0] = name;
00972             cached_servers_ip[0] = ip;
00973             metaserver_save_cache();
00974         }
00975     }
00976 
00977     return 0;
00978 }
00979 
00980 #ifdef MS_STANDALONE
00981 /* This is here just to verify that the code seems to be working
00982  * properly
00983  * To use this code, compile as:
00984  *  gcc -o metaserver -I. -DMS_STANDALONE metaserver.c
00985  */
00986 
00987 int main(int argc, char *argv[])
00988 {
00989     int i;
00990 
00991     metaserver_get_info(META_SERVER, META_PORT);
00992     for (i = 0; i < meta_numservers; i++) {
00993         printf("%s:%d:%s:%d:%s:%s\n",
00994             meta_servers[i].ip_addr,
00995             meta_servers[i].idle_time,
00996             meta_servers[i].hostname,
00997             meta_servers[i].num_players,
00998             meta_servers[i].version,
00999             meta_servers[i].text_comment);
01000     }
01001 }
01002 
01003 #endif
01004 
01005 #ifdef MS2_STANDALONE
01006 /* This is here just to verify that the code seems to be working
01007  * properly
01008  * To use this code, compile as:
01009  *  gcc -o metaserver -I. -DMS2_STANDALONE metaserver.c misc.o -lcurl -lpthread
01010  */
01011 
01012 /* Following lines are to cover external symbols not
01013  * defined - trying to bring in the files the are defined
01014  * in just causes more dependencies, etc.
01015  */
01016 void draw_info(const char *str, int color) { }
01017 int init_connection(char *host, int port) {}
01018 
01019 int metaserver_on=1, meta_port=0;
01020 char *server=NULL, *meta_server;
01021 sint16 use_config[CONFIG_NUMS];
01022 ClientSocket csocket;
01023 
01024 int main(int argc, char *argv[])
01025 {
01026     int i;
01027 
01028     init_metaserver();
01029     metaserver2_get_info();
01030     fprintf(stderr,"Collecting data.");
01031     while (metaserver2_check_status()) {
01032         fprintf(stderr,".");
01033         sleep(1);
01034     }
01035     fprintf(stderr,"\n");
01036     for (i = 0; i < meta_numservers; i++) {
01037         printf("%s:%d:%s:%d:%s:%s\n",
01038             meta_servers[i].ip_addr,
01039             meta_servers[i].idle_time,
01040             meta_servers[i].hostname,
01041             meta_servers[i].num_players,
01042             meta_servers[i].version,
01043             meta_servers[i].text_comment);
01044     }
01045 }
01046 
01047 #endif