Crossfire Server, Trunk
metaserver.cpp
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 <string>
25 
26 #ifndef WIN32 /* ---win32 exclude unix header files */
27 #include <sys/types.h>
28 #include <netinet/in.h>
29 #include <netdb.h>
30 #include <arpa/inet.h>
31 
32 #endif /* end win32 */
33 
34 #include "metaserver2.h"
35 #include "version.h"
36 
37 #ifdef HAVE_LIBCURL
38 #include <curl/curl.h>
39 #include <curl/easy.h>
40 #endif
41 
43 static pthread_mutex_t ms2_info_mutex;
44 
46  /* We could use socket_info.nconns, but that is not quite as accurate,
47  * as connections in the progress of being established, are listening
48  * but don't have a player, etc. The checks below are basically the
49  * same as for the who commands with the addition that WIZ, AFK, and BOT
50  * players are not counted.
51  */
52  char num_players = 0;
53  for (player *pl = first_player; pl != NULL; pl = pl->next) {
54  if (pl->ob->map == NULL)
55  continue;
56  if (pl->hidden)
57  continue;
58  if (QUERY_FLAG(pl->ob, FLAG_WIZ))
59  continue;
60  if (QUERY_FLAG(pl->ob, FLAG_AFK))
61  continue;
62  if (pl->state != ST_PLAYING && pl->state != ST_GET_PARTY_PASSWORD)
63  continue;
64  if (pl->socket->is_bot)
65  continue;
66  num_players++;
67  }
68  return num_players;
69 }
70 
72  int players = 0;
73  player *pl;
74  for (pl = first_player, players = 0; pl != NULL; pl = pl->next, players++);
75  return players;
76 }
77 
84 void metaserver_update(void) {
85 #ifdef HAVE_LIBCURL
86  /* Everything inside the pthread lock/unlock is related
87  * to metaserver2 synchronization.
88  */
89  pthread_mutex_lock(&ms2_info_mutex);
94  pthread_mutex_unlock(&ms2_info_mutex);
95 #endif
96 }
97 
98 /*
99  * Start of metaserver2 logic
100  * Note: All static structures in this file should be treated as strictly
101  * private. The metaserver2 update logic runs in its own thread,
102  * so if something needs to modify these structures, proper locking
103  * is needed.
104  */
105 
107 static std::vector<std::string> metaservers;
108 
119  char *hostname;
121  char *html_comment;
122  char *text_comment;
123  char *archbase;
124  char *mapbase;
125  char *codebase;
126  char *flags;
127 };
128 
131 
134 
149 int metaserver2_init(void) {
150  static int has_init = 0;
151  FILE *fp;
152  char buf[MAX_BUF], *cp, dummy[1];
153  pthread_t thread_id;
154 
155  dummy[0] = '\0';
156 
157 #ifdef HAVE_LIBCURL
158  if (!has_init) {
159  memset(&local_info, 0, sizeof(LocalMeta2Info));
160  memset(&metaserver2_updateinfo, 0, sizeof(MetaServer2_UpdateInfo));
161 
163  pthread_mutex_init(&ms2_info_mutex, NULL);
164  curl_global_init(CURL_GLOBAL_ALL);
165  } else {
167  if (local_info.hostname)
173  if (local_info.archbase)
175  if (local_info.mapbase)
177  if (local_info.codebase)
179  if (local_info.flags)
181  metaservers.clear();
182  }
183 #endif
184 
185  /* Now load up the values from the file */
186  snprintf(buf, sizeof(buf), "%s/metaserver2", settings.confdir);
187 
188  if ((fp = fopen(buf, "r")) == NULL) {
189  LOG(llevError, "Warning: No metaserver2 file found\n");
190  return 0;
191  }
192  while (fgets(buf, sizeof(buf), fp) != NULL) {
193  if (buf[0] == '#')
194  continue;
195  /* eliminate newline */
196  if ((cp = strrchr(buf, '\n')) != NULL)
197  *cp = '\0';
198 
199  /* Skip over empty lines */
200  if (buf[0] == 0)
201  continue;
202 
203  /* Find variable pairs */
204 
205  if ((cp = strpbrk(buf, " \t")) != NULL) {
206  while (isspace(*cp))
207  *cp++ = 0;
208  } else {
209  /* This makes it so we don't have to do NULL checks against
210  * cp everyplace
211  */
212  cp = dummy;
213  }
214 
215  if (!strcasecmp(buf, "metaserver2_notification")) {
216  if (!strcasecmp(cp, "on") || !strcasecmp(cp, "true")) {
218  } else if (!strcasecmp(cp, "off") || !strcasecmp(cp, "false")) {
220  } else {
221  LOG(llevError, "metaserver2: Unknown value for metaserver2_notification: %s\n", cp);
222  }
223  } else if (!strcasecmp(buf, "metaserver2_server")) {
224  if (*cp != 0) {
225  metaservers.push_back(cp);
226  } else {
227  LOG(llevError, "metaserver2: metaserver2_server must have a value.\n");
228  }
229  } else if (!strcasecmp(buf, "localhostname")) {
230  if (*cp != 0) {
232  } else {
233  LOG(llevError, "metaserver2: localhostname must have a value.\n");
234  }
235  } else if (!strcasecmp(buf, "portnumber")) {
236  if (*cp != 0) {
237  local_info.portnumber = atoi(cp);
238  } else {
239  LOG(llevError, "metaserver2: portnumber must have a value.\n");
240  }
241  /* For the following values, it is easier to make sure
242  * the pointers are set to something, even if it is a blank
243  * string, so don't care if there is data in the string or not.
244  */
245  } else if (!strcasecmp(buf, "html_comment")) {
246  local_info.html_comment = strdup(cp);
247  } else if (!strcasecmp(buf, "text_comment")) {
248  local_info.text_comment = strdup(cp);
249  } else if (!strcasecmp(buf, "archbase")) {
250  local_info.archbase = strdup(cp);
251  } else if (!strcasecmp(buf, "mapbase")) {
252  local_info.mapbase = strdup(cp);
253  } else if (!strcasecmp(buf, "codebase")) {
254  local_info.codebase = strdup(cp);
255  } else if (!strcasecmp(buf, "flags")) {
256  local_info.flags = strdup(cp);
257  } else {
258  LOG(llevError, "Unknown value in metaserver2 file: %s\n", buf);
259  }
260  }
261  fclose(fp);
262 
263  /* If no hostname is set, can't do updates */
264  if (!local_info.hostname)
266 
267 #ifndef HAVE_LIBCURL
268  if (local_info.notification) {
269  LOG(llevError, "metaserver2 file is set to do notification, but libcurl is not found.\n");
270  LOG(llevError, "Either fix your compilation, or turn off metaserver2 notification in \n");
271  LOG(llevError, "the %s/metaserver2 file.\n", settings.confdir);
272  LOG(llevError, "Exiting program.\n");
273  exit(1);
274  }
275 #endif
276 
277  if (local_info.notification) {
278  int ret;
279 
280  /* As noted above, it is much easier for the rest of the code
281  * to not have to check for null pointers. So we do that
282  * here, and anything that is null, we just allocate
283  * an empty string.
284  */
286  local_info.html_comment = strdup("");
288  local_info.text_comment = strdup("");
289  if (!local_info.archbase)
290  local_info.archbase = strdup("");
291  if (!local_info.mapbase)
292  local_info.mapbase = strdup("");
293  if (!local_info.codebase)
294  local_info.codebase = strdup("");
295  if (!local_info.flags)
296  local_info.flags = strdup("");
297 
298  ret = pthread_create(&thread_id, NULL, metaserver2_thread, NULL);
299  if (ret) {
300  LOG(llevError, "metaserver2_init: return code from pthread_create() is %d\n", ret);
301 
302  /* Effectively true - we're not going to update the metaserver */
304  }
305  }
306  return local_info.notification;
307 }
308 
320 static size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data) {
321  (void)data;
322  size_t realsize = size*nmemb;
323  LOG(llevError, "Message from metaserver:\n%s\n", (const char*)ptr);
324  return realsize;
325 }
326 
327 #ifdef HAVE_LIBCURL
328 static void metaserver2_build_form(struct curl_httppost **formpost) {
329  struct curl_httppost *lastptr = NULL;
330  char buf[MAX_BUF];
331 
332  /* First, fill in the form - note that everything has to be a string,
333  * so we convert as needed with snprintf.
334  * The order of fields here really isn't important.
335  * The string after CURLFORM_COPYNAME is the name of the POST variable
336  * as the
337  */
338  curl_formadd(formpost, &lastptr,
339  CURLFORM_COPYNAME, "hostname",
340  CURLFORM_COPYCONTENTS, local_info.hostname,
341  CURLFORM_END);
342 
343  snprintf(buf, sizeof(buf), "%d", local_info.portnumber);
344  curl_formadd(formpost, &lastptr,
345  CURLFORM_COPYNAME, "port",
346  CURLFORM_COPYCONTENTS, buf,
347  CURLFORM_END);
348 
349  curl_formadd(formpost, &lastptr,
350  CURLFORM_COPYNAME, "html_comment",
351  CURLFORM_COPYCONTENTS, local_info.html_comment,
352  CURLFORM_END);
353 
354  curl_formadd(formpost, &lastptr,
355  CURLFORM_COPYNAME, "text_comment",
356  CURLFORM_COPYCONTENTS, local_info.text_comment,
357  CURLFORM_END);
358 
359  curl_formadd(formpost, &lastptr,
360  CURLFORM_COPYNAME, "archbase",
361  CURLFORM_COPYCONTENTS, local_info.archbase,
362  CURLFORM_END);
363 
364  curl_formadd(formpost, &lastptr,
365  CURLFORM_COPYNAME, "mapbase",
366  CURLFORM_COPYCONTENTS, local_info.mapbase,
367  CURLFORM_END);
368 
369  curl_formadd(formpost, &lastptr,
370  CURLFORM_COPYNAME, "codebase",
371  CURLFORM_COPYCONTENTS, local_info.codebase,
372  CURLFORM_END);
373 
374  curl_formadd(formpost, &lastptr,
375  CURLFORM_COPYNAME, "flags",
376  CURLFORM_COPYCONTENTS, local_info.flags,
377  CURLFORM_END);
378 
379  pthread_mutex_lock(&ms2_info_mutex);
380 
381  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.num_players);
382  curl_formadd(formpost, &lastptr,
383  CURLFORM_COPYNAME, "num_players",
384  CURLFORM_COPYCONTENTS, buf,
385  CURLFORM_END);
386 
387  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.in_bytes);
388  curl_formadd(formpost, &lastptr,
389  CURLFORM_COPYNAME, "in_bytes",
390  CURLFORM_COPYCONTENTS, buf,
391  CURLFORM_END);
392 
393  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.out_bytes);
394  curl_formadd(formpost, &lastptr,
395  CURLFORM_COPYNAME, "out_bytes",
396  CURLFORM_COPYCONTENTS, buf,
397  CURLFORM_END);
398 
399  snprintf(buf, sizeof(buf), "%ld", (long)metaserver2_updateinfo.uptime);
400  curl_formadd(formpost, &lastptr,
401  CURLFORM_COPYNAME, "uptime",
402  CURLFORM_COPYCONTENTS, buf,
403  CURLFORM_END);
404 
405  pthread_mutex_unlock(&ms2_info_mutex);
406 
407  /* Following few fields are global variables,
408  * but are really defines, so won't ever change.
409  */
410  curl_formadd(formpost, &lastptr,
411  CURLFORM_COPYNAME, "version",
412  CURLFORM_COPYCONTENTS, FULL_VERSION,
413  CURLFORM_END);
414 
415  snprintf(buf, sizeof(buf), "%d", VERSION_SC);
416  curl_formadd(formpost, &lastptr,
417  CURLFORM_COPYNAME, "sc_version",
418  CURLFORM_COPYCONTENTS, buf,
419  CURLFORM_END);
420 
421  snprintf(buf, sizeof(buf), "%d", VERSION_CS);
422  curl_formadd(formpost, &lastptr,
423  CURLFORM_COPYNAME, "cs_version",
424  CURLFORM_COPYCONTENTS, buf,
425  CURLFORM_END);
426 }
427 #endif
428 
434 static void metaserver2_updates(void) {
435 #ifdef HAVE_LIBCURL
436  struct curl_httppost *formpost = NULL;
437  metaserver2_build_form(&formpost);
438 
439  for (auto hostname : metaservers) {
440  CURL *curl = curl_easy_init();
441  if (curl) {
442  /* what URL that receives this POST */
443  curl_easy_setopt(curl, CURLOPT_URL, hostname.c_str());
444  curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
445 
446  /* Almost always, we will get HTTP data returned
447  * to us - instead of it going to stderr,
448  * we want to take care of it ourselves.
449  */
450  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer);
451 
452  char errbuf[CURL_ERROR_SIZE];
453  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
454 
455  CURLcode res = curl_easy_perform(curl);
456  if (res) {
457  LOG(llevError, "metaserver update failed: %s\n", errbuf);
458  }
459 
460  /* always cleanup */
461  curl_easy_cleanup(curl);
462  } else {
463  LOG(llevError, "metaserver: could not initialize curl\n");
464  }
465  }
466  /* then cleanup the formpost chain */
467  curl_formfree(formpost);
468 #endif
469 }
470 
485 void *metaserver2_thread(void *junk) {
486  (void)junk;
487  while (1) {
489  sleep(60);
490  }
491  return NULL;
492 }
global.h
first_player
player * first_player
Definition: init.cpp:106
settings
struct Settings settings
Definition: init.cpp:138
CS_Stats::ibytes
int ibytes
Definition: newclient.h:695
llevError
@ llevError
Definition: logger.h:11
LOG
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.cpp:51
player
Definition: player.h:105
LocalMeta2Info::hostname
char * hostname
Definition: metaserver.cpp:119
strdup_local
#define strdup_local
Definition: compat.h:29
QUERY_FLAG
#define QUERY_FLAG(xyz, p)
Definition: define.h:226
LocalMeta2Info::notification
int notification
Definition: metaserver.cpp:118
FALSE
#define FALSE
Definition: compat.h:14
FULL_VERSION
#define FULL_VERSION
Definition: version.h:4
LocalMeta2Info::html_comment
char * html_comment
Definition: metaserver.cpp:121
LocalMeta2Info::mapbase
char * mapbase
Definition: metaserver.cpp:124
metaserver_update
void metaserver_update(void)
Definition: metaserver.cpp:84
Settings::csport
uint16_t csport
Definition: global.h:242
buf
StringBuffer * buf
Definition: readable.cpp:1551
version.h
LocalMeta2Info::flags
char * flags
Definition: metaserver.cpp:126
CS_Stats::time_start
time_t time_start
Definition: newclient.h:698
metaserver2_init
int metaserver2_init(void)
Definition: metaserver.cpp:149
ms2_info_mutex
static pthread_mutex_t ms2_info_mutex
Definition: metaserver.cpp:43
MetaServer2_UpdateInfo::num_players
int num_players
Definition: metaserver2.h:32
metaserver2_writer
static size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data)
Definition: metaserver.cpp:320
local_info
static LocalMeta2Info local_info
Definition: metaserver.cpp:130
LocalMeta2Info::portnumber
int portnumber
Definition: metaserver.cpp:120
sleep
Definition: sleep.py:1
FLAG_AFK
#define FLAG_AFK
Definition: define.h:368
navar-midane_time.data
data
Definition: navar-midane_time.py:11
Settings::confdir
const char * confdir
Definition: global.h:247
VERSION_SC
#define VERSION_SC
Definition: newserver.h:150
LocalMeta2Info::text_comment
char * text_comment
Definition: metaserver.cpp:122
MetaServer2_UpdateInfo::out_bytes
int out_bytes
Definition: metaserver2.h:34
CS_Stats::obytes
int obytes
Definition: newclient.h:696
nlohmann::detail::void
j template void())
Definition: json.hpp:4099
MAX_BUF
#define MAX_BUF
Definition: define.h:35
metaserver2_thread
void * metaserver2_thread(void *junk)
Definition: metaserver.cpp:485
metaserver2_updateinfo
MetaServer2_UpdateInfo metaserver2_updateinfo
Definition: metaserver.cpp:133
cst_tot
CS_Stats cst_tot
ST_PLAYING
#define ST_PLAYING
Definition: define.h:541
metaservers
static std::vector< std::string > metaservers
Definition: metaserver.cpp:107
FLAG_WIZ
#define FLAG_WIZ
Definition: define.h:231
MetaServer2_UpdateInfo::uptime
time_t uptime
Definition: metaserver2.h:35
metaserver2_updates
static void metaserver2_updates(void)
Definition: metaserver.cpp:434
FREE_AND_CLEAR
#define FREE_AND_CLEAR(xyz)
Definition: global.h:193
players
std::vector< archetype * > players
Definition: player.cpp:493
MetaServer2_UpdateInfo
Definition: metaserver2.h:31
LocalMeta2Info
Definition: metaserver.cpp:117
metaserver2.h
LocalMeta2Info::archbase
char * archbase
Definition: metaserver.cpp:123
strcasecmp
int strcasecmp(const char *s1, const char *s2)
ST_GET_PARTY_PASSWORD
#define ST_GET_PARTY_PASSWORD
Definition: define.h:549
VERSION_CS
#define VERSION_CS
Definition: newserver.h:149
TRUE
#define TRUE
Definition: compat.h:11
MetaServer2_UpdateInfo::in_bytes
int in_bytes
Definition: metaserver2.h:33
altar_valkyrie.pl
pl
Definition: altar_valkyrie.py:28
count_players
int count_players()
Definition: metaserver.cpp:45
altar_valkyrie.res
int res
Definition: altar_valkyrie.py:74
LocalMeta2Info::codebase
char * codebase
Definition: metaserver.cpp:125
count_all_players
int count_all_players()
Definition: metaserver.cpp:71