Crossfire Server, Trunk  R20513
metaserver.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
20 #include "global.h"
21 
22 #include <ctype.h>
23 #include <pthread.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #ifndef WIN32 /* ---win32 exclude unix header files */
28 #include <sys/types.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <arpa/inet.h>
32 
33 #endif /* end win32 */
34 
35 #include "metaserver2.h"
36 #include "version.h"
37 
38 #ifdef HAVE_LIBCURL
39 #include <curl/curl.h>
40 #include <curl/easy.h>
41 #endif
42 
49 void metaserver_update(void) {
50  char num_players = 0;
51  player *pl;
52 
53 #ifdef HAVE_LIBCURL
54  /* We could use socket_info.nconns, but that is not quite as accurate,
55  * as connections in the progress of being established, are listening
56  * but don't have a player, etc. The checks below are basically the
57  * same as for the who commands with the addition that WIZ, AFK, and BOT
58  * players are not counted.
59  */
60  for (pl = first_player; pl != NULL; pl = pl->next) {
61  if (pl->ob->map == NULL)
62  continue;
63  if (pl->hidden)
64  continue;
65  if (QUERY_FLAG(pl->ob, FLAG_WIZ))
66  continue;
67  if (QUERY_FLAG(pl->ob, FLAG_AFK))
68  continue;
69  if (pl->state != ST_PLAYING && pl->state != ST_GET_PARTY_PASSWORD)
70  continue;
71  if (pl->socket.is_bot)
72  continue;
73  num_players++;
74  }
75 
76  /* Everything inside the pthread lock/unlock is related
77  * to metaserver2 synchronization.
78  */
79  pthread_mutex_lock(&ms2_info_mutex);
80  metaserver2_updateinfo.num_players = num_players;
84  pthread_mutex_unlock(&ms2_info_mutex);
85 #endif
86 }
87 
88 /*
89  * Start of metaserver2 logic
90  * Note: All static structures in this file should be treated as strictly
91  * private. The metaserver2 update logic runs in its own thread,
92  * so if something needs to modify these structures, proper locking
93  * is needed.
94  */
95 
100 typedef struct _MetaServer2 {
101  char *hostname;
102  struct _MetaServer2 *next;
103 } MetaServer2;
104 
107 
116 typedef struct _LocalMeta2Info {
118  char *hostname;
120  char *html_comment;
121  char *text_comment;
122  char *archbase;
123  char *mapbase;
124  char *codebase;
125  char *flags;
127 
130 
132 pthread_mutex_t ms2_info_mutex;
133 
136 
144 static void free_metaserver2(MetaServer2 *ms) {
145  free(ms->hostname);
146  free(ms);
147 }
148 
163 int metaserver2_init(void) {
164  static int has_init = 0;
165  FILE *fp;
166  char buf[MAX_BUF], *cp, dummy[1];
167  MetaServer2 *ms2, *msnext;
168  pthread_t thread_id;
169 
170  dummy[0] = '\0';
171 
172 #ifdef HAVE_LIBCURL
173  if (!has_init) {
174  memset(&local_info, 0, sizeof(LocalMeta2Info));
175  memset(&metaserver2_updateinfo, 0, sizeof(MetaServer2_UpdateInfo));
176 
177  local_info.portnumber = settings.csport;
178  metaserver2 = NULL;
179  pthread_mutex_init(&ms2_info_mutex, NULL);
180  curl_global_init(CURL_GLOBAL_ALL);
181  } else {
182  local_info.notification = 0;
183  if (local_info.hostname)
184  FREE_AND_CLEAR(local_info.hostname);
185  if (local_info.html_comment)
186  FREE_AND_CLEAR(local_info.html_comment);
187  if (local_info.text_comment)
188  FREE_AND_CLEAR(local_info.text_comment);
189  if (local_info.archbase)
190  FREE_AND_CLEAR(local_info.archbase);
191  if (local_info.mapbase)
192  FREE_AND_CLEAR(local_info.mapbase);
193  if (local_info.codebase)
194  FREE_AND_CLEAR(local_info.codebase);
195  if (local_info.flags)
196  FREE_AND_CLEAR(local_info.flags);
197  for (ms2 = metaserver2; ms2; ms2 = msnext) {
198  msnext = ms2->next;
199  free_metaserver2(ms2);
200  }
201  metaserver2 = NULL;
202  }
203 #endif
204 
205  /* Now load up the values from the file */
206  snprintf(buf, sizeof(buf), "%s/metaserver2", settings.confdir);
207 
208  if ((fp = fopen(buf, "r")) == NULL) {
209  LOG(llevError, "Warning: No metaserver2 file found\n");
210  return 0;
211  }
212  while (fgets(buf, sizeof(buf), fp) != NULL) {
213  if (buf[0] == '#')
214  continue;
215  /* eliminate newline */
216  if ((cp = strrchr(buf, '\n')) != NULL)
217  *cp = '\0';
218 
219  /* Skip over empty lines */
220  if (buf[0] == 0)
221  continue;
222 
223  /* Find variable pairs */
224 
225  if ((cp = strpbrk(buf, " \t")) != NULL) {
226  while (isspace(*cp))
227  *cp++ = 0;
228  } else {
229  /* This makes it so we don't have to do NULL checks against
230  * cp everyplace
231  */
232  cp = dummy;
233  }
234 
235  if (!strcasecmp(buf, "metaserver2_notification")) {
236  if (!strcasecmp(cp, "on") || !strcasecmp(cp, "true")) {
237  local_info.notification = TRUE;
238  } else if (!strcasecmp(cp, "off") || !strcasecmp(cp, "false")) {
239  local_info.notification = FALSE;
240  } else {
241  LOG(llevError, "metaserver2: Unknown value for metaserver2_notification: %s\n", cp);
242  }
243  } else if (!strcasecmp(buf, "metaserver2_server")) {
244  if (*cp != 0) {
245  ms2 = calloc(1, sizeof(MetaServer2));
246  ms2->hostname = strdup(cp);
247  ms2->next = metaserver2;
248  metaserver2 = ms2;
249  } else {
250  LOG(llevError, "metaserver2: metaserver2_server must have a value.\n");
251  }
252  } else if (!strcasecmp(buf, "localhostname")) {
253  if (*cp != 0) {
254  local_info.hostname = strdup_local(cp);
255  } else {
256  LOG(llevError, "metaserver2: localhostname must have a value.\n");
257  }
258  } else if (!strcasecmp(buf, "portnumber")) {
259  if (*cp != 0) {
260  local_info.portnumber = atoi(cp);
261  } else {
262  LOG(llevError, "metaserver2: portnumber must have a value.\n");
263  }
264  /* For the following values, it is easier to make sure
265  * the pointers are set to something, even if it is a blank
266  * string, so don't care if there is data in the string or not.
267  */
268  } else if (!strcasecmp(buf, "html_comment")) {
269  local_info.html_comment = strdup(cp);
270  } else if (!strcasecmp(buf, "text_comment")) {
271  local_info.text_comment = strdup(cp);
272  } else if (!strcasecmp(buf, "archbase")) {
273  local_info.archbase = strdup(cp);
274  } else if (!strcasecmp(buf, "mapbase")) {
275  local_info.mapbase = strdup(cp);
276  } else if (!strcasecmp(buf, "codebase")) {
277  local_info.codebase = strdup(cp);
278  } else if (!strcasecmp(buf, "flags")) {
279  local_info.flags = strdup(cp);
280  } else {
281  LOG(llevError, "Unknown value in metaserver2 file: %s\n", buf);
282  }
283  }
284  fclose(fp);
285 
286  /* If no hostname is set, can't do updates */
287  if (!local_info.hostname)
288  local_info.notification = 0;
289 
290 #ifndef HAVE_LIBCURL
291  if (local_info.notification) {
292  LOG(llevError, "metaserver2 file is set to do notification, but libcurl is not found.\n");
293  LOG(llevError, "Either fix your compilation, or turn off metaserver2 notification in \n");
294  LOG(llevError, "the %s/metaserver2 file.\n", settings.confdir);
295  LOG(llevError, "Exiting program.\n");
296  exit(1);
297  }
298 #endif
299 
300  if (local_info.notification) {
301  int ret;
302 
303  /* As noted above, it is much easier for the rest of the code
304  * to not have to check for null pointers. So we do that
305  * here, and anything that is null, we just allocate
306  * an empty string.
307  */
308  if (!local_info.html_comment)
309  local_info.html_comment = strdup("");
310  if (!local_info.text_comment)
311  local_info.text_comment = strdup("");
312  if (!local_info.archbase)
313  local_info.archbase = strdup("");
314  if (!local_info.mapbase)
315  local_info.mapbase = strdup("");
316  if (!local_info.codebase)
317  local_info.codebase = strdup("");
318  if (!local_info.flags)
319  local_info.flags = strdup("");
320 
321  ret = pthread_create(&thread_id, NULL, metaserver2_thread, NULL);
322  if (ret) {
323  LOG(llevError, "metaserver2_init: return code from pthread_create() is %d\n", ret);
324 
325  /* Effectively true - we're not going to update the metaserver */
326  local_info.notification = 0;
327  }
328  }
329  return local_info.notification;
330 }
331 
343 static size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data) {
344  size_t realsize = size*nmemb;
345 
346  LOG(llevDebug, "metaserver2_writer- Start of text:\n%s\n", (const char*)ptr);
347  LOG(llevDebug, "metaserver2_writer- End of text:\n");
348 
349  return realsize;
350 }
351 
352 
358 static void metaserver2_updates(void) {
359 #ifdef HAVE_LIBCURL
360  MetaServer2 *ms2;
361  struct curl_httppost *formpost = NULL;
362  struct curl_httppost *lastptr = NULL;
363  char buf[MAX_BUF];
364 
365  /* First, fill in the form - note that everything has to be a string,
366  * so we convert as needed with snprintf.
367  * The order of fields here really isn't important.
368  * The string after CURLFORM_COPYNAME is the name of the POST variable
369  * as the
370  */
371  curl_formadd(&formpost, &lastptr,
372  CURLFORM_COPYNAME, "hostname",
373  CURLFORM_COPYCONTENTS, local_info.hostname,
374  CURLFORM_END);
375 
376  snprintf(buf, sizeof(buf), "%d", local_info.portnumber);
377  curl_formadd(&formpost, &lastptr,
378  CURLFORM_COPYNAME, "port",
379  CURLFORM_COPYCONTENTS, buf,
380  CURLFORM_END);
381 
382  curl_formadd(&formpost, &lastptr,
383  CURLFORM_COPYNAME, "html_comment",
384  CURLFORM_COPYCONTENTS, local_info.html_comment,
385  CURLFORM_END);
386 
387  curl_formadd(&formpost, &lastptr,
388  CURLFORM_COPYNAME, "text_comment",
389  CURLFORM_COPYCONTENTS, local_info.text_comment,
390  CURLFORM_END);
391 
392  curl_formadd(&formpost, &lastptr,
393  CURLFORM_COPYNAME, "archbase",
394  CURLFORM_COPYCONTENTS, local_info.archbase,
395  CURLFORM_END);
396 
397  curl_formadd(&formpost, &lastptr,
398  CURLFORM_COPYNAME, "mapbase",
399  CURLFORM_COPYCONTENTS, local_info.mapbase,
400  CURLFORM_END);
401 
402  curl_formadd(&formpost, &lastptr,
403  CURLFORM_COPYNAME, "codebase",
404  CURLFORM_COPYCONTENTS, local_info.codebase,
405  CURLFORM_END);
406 
407  curl_formadd(&formpost, &lastptr,
408  CURLFORM_COPYNAME, "flags",
409  CURLFORM_COPYCONTENTS, local_info.flags,
410  CURLFORM_END);
411 
412  pthread_mutex_lock(&ms2_info_mutex);
413 
414  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.num_players);
415  curl_formadd(&formpost, &lastptr,
416  CURLFORM_COPYNAME, "num_players",
417  CURLFORM_COPYCONTENTS, buf,
418  CURLFORM_END);
419 
420  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.in_bytes);
421  curl_formadd(&formpost, &lastptr,
422  CURLFORM_COPYNAME, "in_bytes",
423  CURLFORM_COPYCONTENTS, buf,
424  CURLFORM_END);
425 
426  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.out_bytes);
427  curl_formadd(&formpost, &lastptr,
428  CURLFORM_COPYNAME, "out_bytes",
429  CURLFORM_COPYCONTENTS, buf,
430  CURLFORM_END);
431 
432  snprintf(buf, sizeof(buf), "%ld", (long)metaserver2_updateinfo.uptime);
433  curl_formadd(&formpost, &lastptr,
434  CURLFORM_COPYNAME, "uptime",
435  CURLFORM_COPYCONTENTS, buf,
436  CURLFORM_END);
437 
438  pthread_mutex_unlock(&ms2_info_mutex);
439 
440  /* Following few fields are global variables,
441  * but are really defines, so won't ever change.
442  */
443  curl_formadd(&formpost, &lastptr,
444  CURLFORM_COPYNAME, "version",
445  CURLFORM_COPYCONTENTS, FULL_VERSION,
446  CURLFORM_END);
447 
448  snprintf(buf, sizeof(buf), "%d", VERSION_SC);
449  curl_formadd(&formpost, &lastptr,
450  CURLFORM_COPYNAME, "sc_version",
451  CURLFORM_COPYCONTENTS, buf,
452  CURLFORM_END);
453 
454  snprintf(buf, sizeof(buf), "%d", VERSION_CS);
455  curl_formadd(&formpost, &lastptr,
456  CURLFORM_COPYNAME, "cs_version",
457  CURLFORM_COPYCONTENTS, buf,
458  CURLFORM_END);
459 
460  for (ms2 = metaserver2; ms2; ms2 = ms2->next) {
461  CURL *curl;
462  CURLcode res;
463 
464  curl = curl_easy_init();
465  if (curl) {
466  /* what URL that receives this POST */
467  curl_easy_setopt(curl, CURLOPT_URL, ms2->hostname);
468  curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
469 
470  /* Almost always, we will get HTTP data returned
471  * to us - instead of it going to stderr,
472  * we want to take care of it ourselves.
473  */
474  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer);
475  res = curl_easy_perform(curl);
476 
477  if (res)
478  fprintf(stderr, "easy_perform got error %d\n", res);
479 
480  /* always cleanup */
481  curl_easy_cleanup(curl);
482  }
483  }
484  /* then cleanup the formpost chain */
485  curl_formfree(formpost);
486 #endif
487 }
488 
503 void *metaserver2_thread(void *junk) {
504  while (1) {
506  sleep(60);
507  }
508  return NULL;
509 }
static size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data)
Handles writing of HTTP request data from the metaserver2.
Definition: metaserver.c:343
Error, serious thing.
Definition: logger.h:11
char * html_comment
html comment to send to metaservers.
Definition: metaserver.c:120
One player.
Definition: player.h:92
int out_bytes
Number of bytes sent.
Definition: metaserver2.h:36
char * flags
Short flags to send to metaserver.
Definition: metaserver.c:125
#define ST_GET_PARTY_PASSWORD
Player tried to join a password-protected party.
Definition: define.h:585
struct _MetaServer2 * next
Next element in the list.
Definition: metaserver.c:102
int obytes
Definition: newclient.h:695
#define strdup_local
Definition: compat.h:25
LocalMeta2Info basically holds all the non server metaserver2 information that we read from the metas...
Definition: metaserver.c:116
This is a linked list of all the metaservers - never really know how many we have.
Definition: metaserver.c:100
int num_players
Number of players.
Definition: metaserver2.h:34
uint16_t csport
Port for new client/server.
Definition: global.h:239
socket_struct socket
Socket information for this player.
Definition: player.h:94
struct _MetaServer2 MetaServer2
This is a linked list of all the metaservers - never really know how many we have.
#define FLAG_AFK
Player is AFK.
Definition: define.h:377
#define TRUE
Definition: compat.h:10
char * hostname
Hostname to contact.
Definition: metaserver.c:101
time_t uptime
How long server has been up.
Definition: metaserver2.h:37
#define FALSE
Definition: compat.h:11
Global type definitions and header inclusions.
uint32_t hidden
If True, player (DM) is hidden from view.
Definition: player.h:132
#define ST_PLAYING
Usual state.
Definition: define.h:577
void metaserver_update(void)
Updates our info in the metaserver Note that this is used for both metaserver1 and metaserver2 - for ...
Definition: metaserver.c:49
#define VERSION_SC
Definition: newserver.h:162
char * text_comment
text comment to send to metaservers.
Definition: metaserver.c:121
void * metaserver2_thread(void *junk)
metserver2_thread is the function called from pthread_create.
Definition: metaserver.c:503
int in_bytes
Number of bytes received.
Definition: metaserver2.h:35
struct mapdef * map
Pointer to the map in which this object is present.
Definition: object.h:297
int ibytes
ibytes, obytes are bytes in, out.
Definition: newclient.h:694
#define snprintf
Definition: win32.h:46
Structure containing information sent to the metaserver2.
Definition: metaserver2.h:33
uint8_t state
Input state of the player (name, password, etc).
Definition: player.h:118
int metaserver2_init(void)
This initializes the metaserver2 logic - it reads the metaserver2 file, storing the values away...
Definition: metaserver.c:163
struct _LocalMeta2Info LocalMeta2Info
LocalMeta2Info basically holds all the non server metaserver2 information that we read from the metas...
int portnumber
Portnumber of this server.
Definition: metaserver.c:119
MetaServer2_UpdateInfo metaserver2_updateinfo
Statistics on players and such sent to the metaserver2.
Definition: metaserver.c:135
#define VERSION_CS
Version >= 1023 understand setup cmd.
Definition: newserver.h:161
static void free_metaserver2(MetaServer2 *ms)
This frees any data associated with the MetaServer2 info, including the pointer itself.
Definition: metaserver.c:144
#define QUERY_FLAG(xyz, p)
Definition: define.h:225
pthread_mutex_t ms2_info_mutex
Mutex to protect access to metaserver2_updateinfo.
Definition: metaserver.c:132
static LocalMeta2Info local_info
Non volatile information on the server.
Definition: metaserver.c:129
uint32_t is_bot
Client shouldn&#39;t be reported to metaserver.
Definition: newserver.h:119
#define FLAG_WIZ
Object has special privilegies.
Definition: define.h:231
#define MAX_BUF
Used for all kinds of things.
Definition: define.h:35
static void metaserver2_updates(void)
This sends an update to the various metaservers.
Definition: metaserver.c:358
int notification
If true, do updates to metaservers.
Definition: metaserver.c:117
const char * confdir
Configuration files.
Definition: global.h:243
static MetaServer2 * metaserver2
Metaservers to send information to.
Definition: metaserver.c:106
object * ob
The object representing the player.
Definition: player.h:158
#define sleep(x)
Definition: win32.h:150
char * mapbase
and server.
Definition: metaserver.c:123
Only for debugging purposes.
Definition: logger.h:13
CS_Stats cst_tot
struct Settings settings
Server settings.
Definition: init.c:40
This file contains metaserver2 information - the metaserver2 implementation requires that a separate ...
EXTERN player * first_player
First player.
Definition: global.h:117
time_t time_start
When we started logging this.
Definition: newclient.h:697
struct pl * next
Pointer to next player, NULL if this is last.
Definition: player.h:93
int strcasecmp(const char *s1, const char *s2)
Case-insensitive comparaison of strings.
Definition: porting.c:256
char * hostname
Hostname of this server.
Definition: metaserver.c:118
char * strdup(const char *str)
Portable implementation of strdup(3).
Definition: porting.c:200
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.c:51
#define FULL_VERSION
Definition: version.h:6
#define FREE_AND_CLEAR(xyz)
Free the pointer and then set it to NULL.
Definition: global.h:203
char * archbase
Different sources for arches, maps.
Definition: metaserver.c:122