Crossfire Server  1.75.0
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 <mutex>
24 #include <thread>
25 #include <system_error>
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 
44 static std::mutex ms2_info_mutex;
46 static std::timed_mutex ms2_signal;
47 
49  /* We could use socket_info.nconns, but that is not quite as accurate,
50  * as connections in the progress of being established, are listening
51  * but don't have a player, etc. The checks below are basically the
52  * same as for the who commands with the addition that WIZ, AFK, and BOT
53  * players are not counted.
54  */
55  char num_players = 0;
56  for (player *pl = first_player; pl != NULL; pl = pl->next) {
57  if (pl->ob->map == NULL)
58  continue;
59  if (pl->hidden)
60  continue;
61  if (QUERY_FLAG(pl->ob, FLAG_WIZ))
62  continue;
63  if (QUERY_FLAG(pl->ob, FLAG_AFK))
64  continue;
65  if (pl->state != ST_PLAYING && pl->state != ST_GET_PARTY_PASSWORD)
66  continue;
67  if (pl->socket->is_bot)
68  continue;
69  num_players++;
70  }
71  return num_players;
72 }
73 
80 void metaserver_update(void) {
81 #ifdef HAVE_LIBCURL
82  /* Everything inside the lock/unlock is related
83  * to metaserver2 synchronization.
84  */
85  ms2_info_mutex.lock();
87 #ifdef CS_LOGSTATS
91 #else
95 #endif
96  ms2_info_mutex.unlock();
97 #endif
98 }
99 
100 /*
101  * Start of metaserver2 logic
102  * Note: All static structures in this file should be treated as strictly
103  * private. The metaserver2 update logic runs in its own thread,
104  * so if something needs to modify these structures, proper locking
105  * is needed.
106  */
107 
109 static std::vector<std::string> metaservers;
110 
121  char *hostname;
123  char *html_comment;
124  char *text_comment;
125  char *archbase;
126  char *mapbase;
127  char *codebase;
128  char *flags;
129 };
130 
133 
135 static std::thread metaserver_thread;
136 
139 
140 #ifdef HAVE_LIBCURL
141 
153 static size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data) {
154  (void)data;
155  size_t realsize = size*nmemb;
156  LOG(llevError, "Message from metaserver:\n%s\n", (const char*)ptr);
157  return realsize;
158 }
159 
160 static void metaserver2_build_form(struct curl_httppost **formpost) {
161  struct curl_httppost *lastptr = NULL;
162  char buf[MAX_BUF];
163 
164  /* First, fill in the form - note that everything has to be a string,
165  * so we convert as needed with snprintf.
166  * The order of fields here really isn't important.
167  * The string after CURLFORM_COPYNAME is the name of the POST variable
168  * as the
169  */
170  curl_formadd(formpost, &lastptr,
171  CURLFORM_COPYNAME, "hostname",
172  CURLFORM_COPYCONTENTS, local_info.hostname,
173  CURLFORM_END);
174 
175  snprintf(buf, sizeof(buf), "%d", local_info.portnumber);
176  curl_formadd(formpost, &lastptr,
177  CURLFORM_COPYNAME, "port",
178  CURLFORM_COPYCONTENTS, buf,
179  CURLFORM_END);
180 
181  curl_formadd(formpost, &lastptr,
182  CURLFORM_COPYNAME, "html_comment",
183  CURLFORM_COPYCONTENTS, local_info.html_comment,
184  CURLFORM_END);
185 
186  curl_formadd(formpost, &lastptr,
187  CURLFORM_COPYNAME, "text_comment",
188  CURLFORM_COPYCONTENTS, local_info.text_comment,
189  CURLFORM_END);
190 
191  curl_formadd(formpost, &lastptr,
192  CURLFORM_COPYNAME, "archbase",
193  CURLFORM_COPYCONTENTS, local_info.archbase,
194  CURLFORM_END);
195 
196  curl_formadd(formpost, &lastptr,
197  CURLFORM_COPYNAME, "mapbase",
198  CURLFORM_COPYCONTENTS, local_info.mapbase,
199  CURLFORM_END);
200 
201  curl_formadd(formpost, &lastptr,
202  CURLFORM_COPYNAME, "codebase",
203  CURLFORM_COPYCONTENTS, local_info.codebase,
204  CURLFORM_END);
205 
206  curl_formadd(formpost, &lastptr,
207  CURLFORM_COPYNAME, "flags",
208  CURLFORM_COPYCONTENTS, local_info.flags,
209  CURLFORM_END);
210 
211  ms2_info_mutex.lock();
212 
213  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.num_players);
214  curl_formadd(formpost, &lastptr,
215  CURLFORM_COPYNAME, "num_players",
216  CURLFORM_COPYCONTENTS, buf,
217  CURLFORM_END);
218 
219  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.in_bytes);
220  curl_formadd(formpost, &lastptr,
221  CURLFORM_COPYNAME, "in_bytes",
222  CURLFORM_COPYCONTENTS, buf,
223  CURLFORM_END);
224 
225  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.out_bytes);
226  curl_formadd(formpost, &lastptr,
227  CURLFORM_COPYNAME, "out_bytes",
228  CURLFORM_COPYCONTENTS, buf,
229  CURLFORM_END);
230 
231  snprintf(buf, sizeof(buf), "%ld", (long)metaserver2_updateinfo.uptime);
232  curl_formadd(formpost, &lastptr,
233  CURLFORM_COPYNAME, "uptime",
234  CURLFORM_COPYCONTENTS, buf,
235  CURLFORM_END);
236 
237  ms2_info_mutex.unlock();
238 
239  /* Following few fields are global variables,
240  * but are really defines, so won't ever change.
241  */
242  curl_formadd(formpost, &lastptr,
243  CURLFORM_COPYNAME, "version",
244  CURLFORM_COPYCONTENTS, FULL_VERSION,
245  CURLFORM_END);
246 
247  snprintf(buf, sizeof(buf), "%d", VERSION_SC);
248  curl_formadd(formpost, &lastptr,
249  CURLFORM_COPYNAME, "sc_version",
250  CURLFORM_COPYCONTENTS, buf,
251  CURLFORM_END);
252 
253  snprintf(buf, sizeof(buf), "%d", VERSION_CS);
254  curl_formadd(formpost, &lastptr,
255  CURLFORM_COPYNAME, "cs_version",
256  CURLFORM_COPYCONTENTS, buf,
257  CURLFORM_END);
258 }
259 #endif
260 
266 static void metaserver2_updates(void) {
267 #ifdef HAVE_LIBCURL
268  struct curl_httppost *formpost = NULL;
269  metaserver2_build_form(&formpost);
270 
271  for (auto hostname : metaservers) {
272  CURL *curl = curl_easy_init();
273  if (curl) {
274  curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30000);
275 
276  /* what URL that receives this POST */
277  curl_easy_setopt(curl, CURLOPT_URL, hostname.c_str());
278  curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
279 
280  /* Almost always, we will get HTTP data returned
281  * to us - instead of it going to stderr,
282  * we want to take care of it ourselves.
283  */
284  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer);
285 
286  char errbuf[CURL_ERROR_SIZE];
287  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
288 
289  CURLcode res = curl_easy_perform(curl);
290  if (res) {
291  LOG(llevError, "metaserver update failed: %s\n", errbuf);
292  }
293 
294  /* always cleanup */
295  curl_easy_cleanup(curl);
296  } else {
297  LOG(llevError, "metaserver: could not initialize curl\n");
298  }
299  }
300  /* then cleanup the formpost chain */
301  curl_formfree(formpost);
302 #endif
303 }
304 
311  do {
313  } while (!ms2_signal.try_lock_for(std::chrono::seconds(60)));
314  ms2_signal.unlock();
315 }
316 
331 int metaserver2_init(void) {
332  FILE *fp;
333  char buf[MAX_BUF], *cp, dummy[1];
334 
335  dummy[0] = '\0';
336 
337 #ifdef HAVE_LIBCURL
338  static int has_init = 0;
339  if (!has_init) {
340  memset(&local_info, 0, sizeof(LocalMeta2Info));
341  memset(&metaserver2_updateinfo, 0, sizeof(MetaServer2_UpdateInfo));
342 
343  local_info.portnumber = settings.csport;
344  curl_global_init(CURL_GLOBAL_ALL);
345  } else {
346  local_info.notification = 0;
347  if (local_info.hostname)
348  FREE_AND_CLEAR(local_info.hostname);
349  if (local_info.html_comment)
350  FREE_AND_CLEAR(local_info.html_comment);
351  if (local_info.text_comment)
352  FREE_AND_CLEAR(local_info.text_comment);
353  if (local_info.archbase)
354  FREE_AND_CLEAR(local_info.archbase);
355  if (local_info.mapbase)
356  FREE_AND_CLEAR(local_info.mapbase);
357  if (local_info.codebase)
358  FREE_AND_CLEAR(local_info.codebase);
359  if (local_info.flags)
360  FREE_AND_CLEAR(local_info.flags);
361  metaservers.clear();
362  }
363 #endif
364 
365  /* Now load up the values from the file */
366  snprintf(buf, sizeof(buf), "%s/metaserver2", settings.confdir);
367 
368  if ((fp = fopen(buf, "r")) == NULL) {
369  LOG(llevError, "Warning: No metaserver2 file found\n");
370  return 0;
371  }
372  while (fgets(buf, sizeof(buf), fp) != NULL) {
373  if (buf[0] == '#')
374  continue;
375  /* eliminate newline */
376  if ((cp = strrchr(buf, '\n')) != NULL)
377  *cp = '\0';
378 
379  /* Skip over empty lines */
380  if (buf[0] == 0)
381  continue;
382 
383  /* Find variable pairs */
384 
385  if ((cp = strpbrk(buf, " \t")) != NULL) {
386  while (isspace(*cp))
387  *cp++ = 0;
388  } else {
389  /* This makes it so we don't have to do NULL checks against
390  * cp everyplace
391  */
392  cp = dummy;
393  }
394 
395  if (!strcasecmp(buf, "metaserver2_notification")) {
396  if (!strcasecmp(cp, "on") || !strcasecmp(cp, "true")) {
397  local_info.notification = TRUE;
398  } else if (!strcasecmp(cp, "off") || !strcasecmp(cp, "false")) {
399  local_info.notification = FALSE;
400  } else {
401  LOG(llevError, "metaserver2: Unknown value for metaserver2_notification: %s\n", cp);
402  }
403  } else if (!strcasecmp(buf, "metaserver2_server")) {
404  if (*cp != 0) {
405  metaservers.push_back(cp);
406  } else {
407  LOG(llevError, "metaserver2: metaserver2_server must have a value.\n");
408  }
409  } else if (!strcasecmp(buf, "localhostname")) {
410  if (*cp != 0) {
411  local_info.hostname = strdup_local(cp);
412  } else {
413  LOG(llevError, "metaserver2: localhostname must have a value.\n");
414  }
415  } else if (!strcasecmp(buf, "portnumber")) {
416  if (*cp != 0) {
417  local_info.portnumber = atoi(cp);
418  } else {
419  LOG(llevError, "metaserver2: portnumber must have a value.\n");
420  }
421  /* For the following values, it is easier to make sure
422  * the pointers are set to something, even if it is a blank
423  * string, so don't care if there is data in the string or not.
424  */
425  } else if (!strcasecmp(buf, "html_comment")) {
426  local_info.html_comment = strdup(cp);
427  } else if (!strcasecmp(buf, "text_comment")) {
428  local_info.text_comment = strdup(cp);
429  } else if (!strcasecmp(buf, "archbase")) {
430  local_info.archbase = strdup(cp);
431  } else if (!strcasecmp(buf, "mapbase")) {
432  local_info.mapbase = strdup(cp);
433  } else if (!strcasecmp(buf, "codebase")) {
434  local_info.codebase = strdup(cp);
435  } else if (!strcasecmp(buf, "flags")) {
436  local_info.flags = strdup(cp);
437  } else {
438  LOG(llevError, "Unknown value in metaserver2 file: %s\n", buf);
439  }
440  }
441  fclose(fp);
442 
443  /* If no hostname is set, can't do updates */
444  if (!local_info.hostname)
445  local_info.notification = 0;
446 
447 #ifndef HAVE_LIBCURL
448  if (local_info.notification) {
449  LOG(llevError, "metaserver2 file is set to do notification, but libcurl is not found.\n");
450  LOG(llevError, "Either fix your compilation, or turn off metaserver2 notification in \n");
451  LOG(llevError, "the %s/metaserver2 file.\n", settings.confdir);
452  LOG(llevError, "Exiting program.\n");
453  exit(1);
454  }
455 #endif
456 
457  if (local_info.notification) {
458  /* As noted above, it is much easier for the rest of the code
459  * to not have to check for null pointers. So we do that
460  * here, and anything that is null, we just allocate
461  * an empty string.
462  */
463  if (!local_info.html_comment)
464  local_info.html_comment = strdup("");
465  if (!local_info.text_comment)
466  local_info.text_comment = strdup("");
467  if (!local_info.archbase)
468  local_info.archbase = strdup("");
469  if (!local_info.mapbase)
470  local_info.mapbase = strdup("");
471  if (!local_info.codebase)
472  local_info.codebase = strdup("");
473  if (!local_info.flags)
474  local_info.flags = strdup("");
475 
476  ms2_signal.lock();
477  try {
478  metaserver_thread = std::thread(metaserver2_thread);
479  }
480  catch (const std::system_error &err) {
481  LOG(llevError, "metaserver2_init: failed to create thread, code %d, what %s\n", err.code().value(), err.what());
482  /* Effectively true - we're not going to update the metaserver */
483  local_info.notification = 0;
484  }
485  }
486  return local_info.notification;
487 }
488 
493  ms2_signal.unlock();
494  if (metaserver_thread.joinable()) {
495  metaserver_thread.join();
496  }
497 }
Error, serious thing.
Definition: logger.h:11
#define ST_GET_PARTY_PASSWORD
Player tried to join a password-protected party.
Definition: define.h:570
char * hostname
Hostname of this server.
Definition: metaserver.cpp:121
Structure containing information sent to the metaserver2.
Definition: metaserver2.h:31
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.cpp:58
int obytes
Definition: newclient.h:736
int notification
If true, do updates to metaservers.
Definition: metaserver.cpp:120
void metaserver2_exit()
Stop metaserver updates.
Definition: metaserver.cpp:492
#define strdup_local
Definition: compat.h:29
player * first_player
First player.
Definition: init.cpp:106
uint16_t csport
Port for new client/server.
Definition: global.h:243
MetaServer2_UpdateInfo metaserver2_updateinfo
Statistics on players and such sent to the metaserver2.
Definition: metaserver.cpp:138
char * flags
Short flags to send to metaserver.
Definition: metaserver.cpp:128
static void metaserver2_updates(void)
This sends an update to the various metaservers.
Definition: metaserver.cpp:266
static std::vector< std::string > metaservers
Metaservers to send information to, list of hostnames.
Definition: metaserver.cpp:109
static std::mutex ms2_info_mutex
Mutex to protect access to metaserver2_updateinfo.
Definition: metaserver.cpp:44
#define FLAG_AFK
Player is AFK.
Definition: define.h:364
#define TRUE
Definition: compat.h:11
char * text_comment
text comment to send to metaservers.
Definition: metaserver.cpp:124
#define FALSE
Definition: compat.h:14
Global type definitions and header inclusions.
int portnumber
Portnumber of this server.
Definition: metaserver.cpp:122
#define ST_PLAYING
Usual state.
Definition: define.h:562
#define VERSION_SC
Definition: newserver.h:154
int out_bytes
Number of bytes sent.
Definition: metaserver2.h:34
player * next
Pointer to next player, NULL if this is last.
Definition: player.h:108
#define QUERY_FLAG(xyz, p)
Definition: define.h:386
int num_players
Number of players.
Definition: metaserver2.h:32
int ibytes
ibytes, obytes are bytes in, out.
Definition: newclient.h:735
int strcasecmp(const char *s1, const char *s2)
int count_players()
Definition: metaserver.cpp:48
int in_bytes
Number of bytes received.
Definition: metaserver2.h:33
char * mapbase
and server.
Definition: metaserver.cpp:126
time_t uptime
How long server has been up.
Definition: metaserver2.h:35
static std::timed_mutex ms2_signal
Mutex to signal the thread should stop.
Definition: metaserver.cpp:46
struct Settings settings
Global settings.
Definition: init.cpp:139
#define VERSION_CS
Version >= 1023 understand setup cmd.
Definition: newserver.h:153
#define FLAG_WIZ
Object has special privilegies.
Definition: define.h:218
long seconds(void)
Return wall clock time in seconds.
Definition: time.cpp:348
#define MAX_BUF
Used for all kinds of things.
Definition: define.h:35
const char * confdir
Configuration files.
Definition: global.h:248
char * archbase
Different sources for arches, maps.
Definition: metaserver.cpp:125
CS_Stats cst_tot
int metaserver2_init(void)
This initializes the metaserver2 logic - it reads the metaserver2 file, storing the values away...
Definition: metaserver.cpp:331
void metaserver2_thread()
Repeatedly send updates to the metaserver, stopping when ms2_signal is acquired.
Definition: metaserver.cpp:310
char * html_comment
html comment to send to metaservers.
Definition: metaserver.cpp:123
This file contains metaserver2 information - the metaserver2 implementation requires that a separate ...
void metaserver_update(void)
Updates our info in the metaserver Note that this is used for both metaserver1 and metaserver2 - for ...
Definition: metaserver.cpp:80
time_t time_start
Definition: newclient.h:738
LocalMeta2Info basically holds all the non server metaserver2 information that we read from the metas...
Definition: metaserver.cpp:119
One player.
Definition: player.h:107
StringBuffer * buf
Definition: readable.cpp:1563
#define FULL_VERSION
Definition: version.h:6
#define FREE_AND_CLEAR(xyz)
Free the pointer and then set it to NULL.
Definition: global.h:195
static LocalMeta2Info local_info
Non volatile information on the server.
Definition: metaserver.cpp:132
static std::thread metaserver_thread
Metaserver thread, if notifications are enabled.
Definition: metaserver.cpp:135