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 
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();
90  ms2_info_mutex.unlock();
91 #endif
92 }
93 
94 /*
95  * Start of metaserver2 logic
96  * Note: All static structures in this file should be treated as strictly
97  * private. The metaserver2 update logic runs in its own thread,
98  * so if something needs to modify these structures, proper locking
99  * is needed.
100  */
101 
103 static std::vector<std::string> metaservers;
104 
115  char *hostname;
117  char *html_comment;
118  char *text_comment;
119  char *archbase;
120  char *mapbase;
121  char *codebase;
122  char *flags;
123 };
124 
127 
129 static std::thread metaserver_thread;
130 
133 
145 static size_t metaserver2_writer(void *ptr, size_t size, size_t nmemb, void *data) {
146  (void)data;
147  size_t realsize = size*nmemb;
148  LOG(llevError, "Message from metaserver:\n%s\n", (const char*)ptr);
149  return realsize;
150 }
151 
152 #ifdef HAVE_LIBCURL
153 static void metaserver2_build_form(struct curl_httppost **formpost) {
154  struct curl_httppost *lastptr = NULL;
155  char buf[MAX_BUF];
156 
157  /* First, fill in the form - note that everything has to be a string,
158  * so we convert as needed with snprintf.
159  * The order of fields here really isn't important.
160  * The string after CURLFORM_COPYNAME is the name of the POST variable
161  * as the
162  */
163  curl_formadd(formpost, &lastptr,
164  CURLFORM_COPYNAME, "hostname",
165  CURLFORM_COPYCONTENTS, local_info.hostname,
166  CURLFORM_END);
167 
168  snprintf(buf, sizeof(buf), "%d", local_info.portnumber);
169  curl_formadd(formpost, &lastptr,
170  CURLFORM_COPYNAME, "port",
171  CURLFORM_COPYCONTENTS, buf,
172  CURLFORM_END);
173 
174  curl_formadd(formpost, &lastptr,
175  CURLFORM_COPYNAME, "html_comment",
176  CURLFORM_COPYCONTENTS, local_info.html_comment,
177  CURLFORM_END);
178 
179  curl_formadd(formpost, &lastptr,
180  CURLFORM_COPYNAME, "text_comment",
181  CURLFORM_COPYCONTENTS, local_info.text_comment,
182  CURLFORM_END);
183 
184  curl_formadd(formpost, &lastptr,
185  CURLFORM_COPYNAME, "archbase",
186  CURLFORM_COPYCONTENTS, local_info.archbase,
187  CURLFORM_END);
188 
189  curl_formadd(formpost, &lastptr,
190  CURLFORM_COPYNAME, "mapbase",
191  CURLFORM_COPYCONTENTS, local_info.mapbase,
192  CURLFORM_END);
193 
194  curl_formadd(formpost, &lastptr,
195  CURLFORM_COPYNAME, "codebase",
196  CURLFORM_COPYCONTENTS, local_info.codebase,
197  CURLFORM_END);
198 
199  curl_formadd(formpost, &lastptr,
200  CURLFORM_COPYNAME, "flags",
201  CURLFORM_COPYCONTENTS, local_info.flags,
202  CURLFORM_END);
203 
204  ms2_info_mutex.lock();
205 
206  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.num_players);
207  curl_formadd(formpost, &lastptr,
208  CURLFORM_COPYNAME, "num_players",
209  CURLFORM_COPYCONTENTS, buf,
210  CURLFORM_END);
211 
212  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.in_bytes);
213  curl_formadd(formpost, &lastptr,
214  CURLFORM_COPYNAME, "in_bytes",
215  CURLFORM_COPYCONTENTS, buf,
216  CURLFORM_END);
217 
218  snprintf(buf, sizeof(buf), "%d", metaserver2_updateinfo.out_bytes);
219  curl_formadd(formpost, &lastptr,
220  CURLFORM_COPYNAME, "out_bytes",
221  CURLFORM_COPYCONTENTS, buf,
222  CURLFORM_END);
223 
224  snprintf(buf, sizeof(buf), "%ld", (long)metaserver2_updateinfo.uptime);
225  curl_formadd(formpost, &lastptr,
226  CURLFORM_COPYNAME, "uptime",
227  CURLFORM_COPYCONTENTS, buf,
228  CURLFORM_END);
229 
230  ms2_info_mutex.unlock();
231 
232  /* Following few fields are global variables,
233  * but are really defines, so won't ever change.
234  */
235  curl_formadd(formpost, &lastptr,
236  CURLFORM_COPYNAME, "version",
237  CURLFORM_COPYCONTENTS, FULL_VERSION,
238  CURLFORM_END);
239 
240  snprintf(buf, sizeof(buf), "%d", VERSION_SC);
241  curl_formadd(formpost, &lastptr,
242  CURLFORM_COPYNAME, "sc_version",
243  CURLFORM_COPYCONTENTS, buf,
244  CURLFORM_END);
245 
246  snprintf(buf, sizeof(buf), "%d", VERSION_CS);
247  curl_formadd(formpost, &lastptr,
248  CURLFORM_COPYNAME, "cs_version",
249  CURLFORM_COPYCONTENTS, buf,
250  CURLFORM_END);
251 }
252 #endif
253 
259 static void metaserver2_updates(void) {
260 #ifdef HAVE_LIBCURL
261  struct curl_httppost *formpost = NULL;
262  metaserver2_build_form(&formpost);
263 
264  for (auto hostname : metaservers) {
265  CURL *curl = curl_easy_init();
266  if (curl) {
267  curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30000);
268 
269  /* what URL that receives this POST */
270  curl_easy_setopt(curl, CURLOPT_URL, hostname.c_str());
271  curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
272 
273  /* Almost always, we will get HTTP data returned
274  * to us - instead of it going to stderr,
275  * we want to take care of it ourselves.
276  */
277  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metaserver2_writer);
278 
279  char errbuf[CURL_ERROR_SIZE];
280  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
281 
282  CURLcode res = curl_easy_perform(curl);
283  if (res) {
284  LOG(llevError, "metaserver update failed: %s\n", errbuf);
285  }
286 
287  /* always cleanup */
288  curl_easy_cleanup(curl);
289  } else {
290  LOG(llevError, "metaserver: could not initialize curl\n");
291  }
292  }
293  /* then cleanup the formpost chain */
294  curl_formfree(formpost);
295 #endif
296 }
297 
304  do {
306  } while (!ms2_signal.try_lock_for(std::chrono::seconds(60)));
307  ms2_signal.unlock();
308 }
309 
324 int metaserver2_init(void) {
325  static int has_init = 0;
326  FILE *fp;
327  char buf[MAX_BUF], *cp, dummy[1];
328 
329  dummy[0] = '\0';
330 
331 #ifdef HAVE_LIBCURL
332  if (!has_init) {
333  memset(&local_info, 0, sizeof(LocalMeta2Info));
334  memset(&metaserver2_updateinfo, 0, sizeof(MetaServer2_UpdateInfo));
335 
337  curl_global_init(CURL_GLOBAL_ALL);
338  } else {
340  if (local_info.hostname)
346  if (local_info.archbase)
348  if (local_info.mapbase)
350  if (local_info.codebase)
352  if (local_info.flags)
354  metaservers.clear();
355  }
356 #endif
357 
358  /* Now load up the values from the file */
359  snprintf(buf, sizeof(buf), "%s/metaserver2", settings.confdir);
360 
361  if ((fp = fopen(buf, "r")) == NULL) {
362  LOG(llevError, "Warning: No metaserver2 file found\n");
363  return 0;
364  }
365  while (fgets(buf, sizeof(buf), fp) != NULL) {
366  if (buf[0] == '#')
367  continue;
368  /* eliminate newline */
369  if ((cp = strrchr(buf, '\n')) != NULL)
370  *cp = '\0';
371 
372  /* Skip over empty lines */
373  if (buf[0] == 0)
374  continue;
375 
376  /* Find variable pairs */
377 
378  if ((cp = strpbrk(buf, " \t")) != NULL) {
379  while (isspace(*cp))
380  *cp++ = 0;
381  } else {
382  /* This makes it so we don't have to do NULL checks against
383  * cp everyplace
384  */
385  cp = dummy;
386  }
387 
388  if (!strcasecmp(buf, "metaserver2_notification")) {
389  if (!strcasecmp(cp, "on") || !strcasecmp(cp, "true")) {
391  } else if (!strcasecmp(cp, "off") || !strcasecmp(cp, "false")) {
393  } else {
394  LOG(llevError, "metaserver2: Unknown value for metaserver2_notification: %s\n", cp);
395  }
396  } else if (!strcasecmp(buf, "metaserver2_server")) {
397  if (*cp != 0) {
398  metaservers.push_back(cp);
399  } else {
400  LOG(llevError, "metaserver2: metaserver2_server must have a value.\n");
401  }
402  } else if (!strcasecmp(buf, "localhostname")) {
403  if (*cp != 0) {
405  } else {
406  LOG(llevError, "metaserver2: localhostname must have a value.\n");
407  }
408  } else if (!strcasecmp(buf, "portnumber")) {
409  if (*cp != 0) {
410  local_info.portnumber = atoi(cp);
411  } else {
412  LOG(llevError, "metaserver2: portnumber must have a value.\n");
413  }
414  /* For the following values, it is easier to make sure
415  * the pointers are set to something, even if it is a blank
416  * string, so don't care if there is data in the string or not.
417  */
418  } else if (!strcasecmp(buf, "html_comment")) {
419  local_info.html_comment = strdup(cp);
420  } else if (!strcasecmp(buf, "text_comment")) {
421  local_info.text_comment = strdup(cp);
422  } else if (!strcasecmp(buf, "archbase")) {
423  local_info.archbase = strdup(cp);
424  } else if (!strcasecmp(buf, "mapbase")) {
425  local_info.mapbase = strdup(cp);
426  } else if (!strcasecmp(buf, "codebase")) {
427  local_info.codebase = strdup(cp);
428  } else if (!strcasecmp(buf, "flags")) {
429  local_info.flags = strdup(cp);
430  } else {
431  LOG(llevError, "Unknown value in metaserver2 file: %s\n", buf);
432  }
433  }
434  fclose(fp);
435 
436  /* If no hostname is set, can't do updates */
437  if (!local_info.hostname)
439 
440 #ifndef HAVE_LIBCURL
441  if (local_info.notification) {
442  LOG(llevError, "metaserver2 file is set to do notification, but libcurl is not found.\n");
443  LOG(llevError, "Either fix your compilation, or turn off metaserver2 notification in \n");
444  LOG(llevError, "the %s/metaserver2 file.\n", settings.confdir);
445  LOG(llevError, "Exiting program.\n");
446  exit(1);
447  }
448 #endif
449 
450  if (local_info.notification) {
451  /* As noted above, it is much easier for the rest of the code
452  * to not have to check for null pointers. So we do that
453  * here, and anything that is null, we just allocate
454  * an empty string.
455  */
457  local_info.html_comment = strdup("");
459  local_info.text_comment = strdup("");
460  if (!local_info.archbase)
461  local_info.archbase = strdup("");
462  if (!local_info.mapbase)
463  local_info.mapbase = strdup("");
464  if (!local_info.codebase)
465  local_info.codebase = strdup("");
466  if (!local_info.flags)
467  local_info.flags = strdup("");
468 
469  ms2_signal.lock();
470  try {
471  metaserver_thread = std::thread(metaserver2_thread);
472  }
473  catch (const std::system_error &err) {
474  LOG(llevError, "metaserver2_init: failed to create thread, code %d, what %s\n", err.code().value(), err.what());
475  /* Effectively true - we're not going to update the metaserver */
477  }
478  }
479  return local_info.notification;
480 }
481 
486  ms2_signal.unlock();
487  if (metaserver_thread.joinable()) {
488  metaserver_thread.join();
489  }
490 }
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:51
player
Definition: player.h:105
LocalMeta2Info::hostname
char * hostname
Definition: metaserver.cpp:115
strdup_local
#define strdup_local
Definition: compat.h:29
ms2_signal
static std::timed_mutex ms2_signal
Definition: metaserver.cpp:46
QUERY_FLAG
#define QUERY_FLAG(xyz, p)
Definition: define.h:226
LocalMeta2Info::notification
int notification
Definition: metaserver.cpp:114
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:117
LocalMeta2Info::mapbase
char * mapbase
Definition: metaserver.cpp:120
metaserver_update
void metaserver_update(void)
Definition: metaserver.cpp:80
Settings::csport
uint16_t csport
Definition: global.h:242
buf
StringBuffer * buf
Definition: readable.cpp:1552
version.h
LocalMeta2Info::flags
char * flags
Definition: metaserver.cpp:122
metaserver2_thread
void metaserver2_thread()
Definition: metaserver.cpp:303
CS_Stats::time_start
time_t time_start
Definition: newclient.h:687
metaserver2_init
int metaserver2_init(void)
Definition: metaserver.cpp:324
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:145
local_info
static LocalMeta2Info local_info
Definition: metaserver.cpp:126
LocalMeta2Info::portnumber
int portnumber
Definition: metaserver.cpp:116
metaserver_thread
static std::thread metaserver_thread
Definition: metaserver.cpp:129
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:118
MetaServer2_UpdateInfo::out_bytes
int out_bytes
Definition: metaserver2.h:34
metaserver2_exit
void metaserver2_exit()
Definition: metaserver.cpp:485
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:344
MAX_BUF
#define MAX_BUF
Definition: define.h:35
metaserver2_updateinfo
MetaServer2_UpdateInfo metaserver2_updateinfo
Definition: metaserver.cpp:132
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:103
FLAG_WIZ
#define FLAG_WIZ
Definition: define.h:231
ms2_info_mutex
static std::mutex ms2_info_mutex
Definition: metaserver.cpp:44
MetaServer2_UpdateInfo::uptime
time_t uptime
Definition: metaserver2.h:35
metaserver2_updates
static void metaserver2_updates(void)
Definition: metaserver.cpp:259
FREE_AND_CLEAR
#define FREE_AND_CLEAR(xyz)
Definition: global.h:193
MetaServer2_UpdateInfo
Definition: metaserver2.h:31
LocalMeta2Info
Definition: metaserver.cpp:113
metaserver2.h
LocalMeta2Info::archbase
char * archbase
Definition: metaserver.cpp:119
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:48
altar_valkyrie.res
int res
Definition: altar_valkyrie.py:74
LocalMeta2Info::codebase
char * codebase
Definition: metaserver.cpp:121