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