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