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 }
LocalMeta2Info::flags
char * flags
Definition: metaserver.cpp:122
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
MetaServer2_UpdateInfo::num_players
int num_players
Definition: metaserver2.h:32
LocalMeta2Info::mapbase
char * mapbase
Definition: metaserver.cpp:120
llevError
@ llevError
Definition: logger.h:11
LOG
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.cpp:51
player
Definition: player.h:105
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
FALSE
#define FALSE
Definition: compat.h:14
FULL_VERSION
#define FULL_VERSION
Definition: version.h:4
time
non standard information is not specified or uptime this means how long since the executable has been started A particular host may have been running a server for quite a long time
Definition: arch-handbook.txt:206
MetaServer2_UpdateInfo::in_bytes
int in_bytes
Definition: metaserver2.h:33
LocalMeta2Info
Definition: metaserver.cpp:113
LocalMeta2Info::archbase
char * archbase
Definition: metaserver.cpp:119
MetaServer2_UpdateInfo::out_bytes
int out_bytes
Definition: metaserver2.h:34
CS_Stats::obytes
int obytes
Definition: newclient.h:685
metaserver_update
void metaserver_update(void)
Definition: metaserver.cpp:80
buf
StringBuffer * buf
Definition: readable.cpp:1552
LocalMeta2Info::notification
int notification
Definition: metaserver.cpp:114
version.h
metaserver2_thread
void metaserver2_thread()
Definition: metaserver.cpp:303
LocalMeta2Info::hostname
char * hostname
Definition: metaserver.cpp:115
metaserver2_init
int metaserver2_init(void)
Definition: metaserver.cpp:324
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
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
VERSION_SC
#define VERSION_SC
Definition: newserver.h:150
Settings::csport
uint16_t csport
Definition: global.h:242
metaserver2_exit
void metaserver2_exit()
Definition: metaserver.cpp:485
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
LocalMeta2Info::codebase
char * codebase
Definition: metaserver.cpp:121
metaserver2_updateinfo
MetaServer2_UpdateInfo metaserver2_updateinfo
Definition: metaserver.cpp:132
cst_tot
CS_Stats cst_tot
LocalMeta2Info::text_comment
char * text_comment
Definition: metaserver.cpp:118
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_updates
static void metaserver2_updates(void)
Definition: metaserver.cpp:259
FREE_AND_CLEAR
#define FREE_AND_CLEAR(xyz)
Definition: global.h:193
num_players
non standard information is not specified num_players
Definition: arch-handbook.txt:200
metaserver2.h
exit
Install Bug reporting Credits but rather whatever guild name you are using *With the current map and server there are three they and GreenGoblin *Whatever name you give the folder should but it will still use GUILD_TEMPLATE *You can change what guild it uses by editing the map files Modify Map or objects if you want to use the optional Python based Guild Storage hall The first three are on the main the next two are in the guild_hq and the final one is in hallofjoining Withe the Storage three objects are found on the main floor and the last two are in the basement It s not that but you will need a map editor You find the object that has the click edit and change the line script options(which currently is "GUILD_TEMPALTE") to the guild you wish to use. And make sure you use the same one for all of them or it won 't work. Here 's a quick HOWTO for using the map editor to make these changes edit the mainfloor map exit(x15, y29 - set to/Edit/This/Exit/Path in the template) back to the world map as well. If you are using the Storage Hall map(storage_hall)
MetaServer2_UpdateInfo::uptime
time_t uptime
Definition: metaserver2.h:35
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
CS_Stats::time_start
time_t time_start
Definition: newclient.h:687
MetaServer2_UpdateInfo
Definition: metaserver2.h:31
LocalMeta2Info::html_comment
char * html_comment
Definition: metaserver.cpp:117
TRUE
#define TRUE
Definition: compat.h:11
LocalMeta2Info::portnumber
int portnumber
Definition: metaserver.cpp:116
altar_valkyrie.pl
pl
Definition: altar_valkyrie.py:28
count_players
int count_players()
Definition: metaserver.cpp:48
Settings::confdir
const char * confdir
Definition: global.h:247
altar_valkyrie.res
int res
Definition: altar_valkyrie.py:74