1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
15 #include <fc_config.h>
18 #include "fc_prehdrs.h"
26 #ifdef HAVE_NETINET_IN_H
27 #include <netinet/in.h>
29 #ifdef HAVE_SYS_SOCKET_H
30 #include <sys/socket.h>
32 #ifdef HAVE_SYS_TYPES_H
33 #include <sys/types.h>
38 #ifdef HAVE_ARPA_INET_H
39 #include <arpa/inet.h>
54 #include "connection.h"
68 static bool server_is_open
= FALSE
;
69 static bool persistent_meta_connection
= FALSE
;
70 static int meta_retry_wait
= 0;
72 static char meta_patches
[256] = "";
73 static char meta_message
[256] = "";
75 static fc_thread
*meta_srv_thread
= NULL
;
77 /*************************************************************************
78 the default metaserver patches for this server
79 *************************************************************************/
80 const char *default_meta_patches_string(void)
85 /*************************************************************************
86 Return static string with default info line to send to metaserver.
87 *************************************************************************/
88 const char *default_meta_message_string(void)
91 return "unstable pre-" NEXT_STABLE_VERSION
": beware";
92 #else /* IS_BETA_VERSION */
94 return "development version: beware";
95 #else /* IS_DEVEL_VERSION */
97 #endif /* IS_DEVEL_VERSION */
98 #endif /* IS_BETA_VERSION */
101 /*************************************************************************
102 the metaserver patches
103 *************************************************************************/
104 const char *get_meta_patches_string(void)
109 /*************************************************************************
110 the metaserver message
111 *************************************************************************/
112 const char *get_meta_message_string(void)
117 /*************************************************************************
118 The server metaserver type
119 *************************************************************************/
120 static const char *get_meta_type_string(void)
122 if (game
.server
.meta_info
.type
[0] != '\0') {
123 return game
.server
.meta_info
.type
;
129 /*************************************************************************
130 The metaserver message set by user
131 *************************************************************************/
132 const char *get_user_meta_message_string(void)
134 if (game
.server
.meta_info
.user_message
[0] != '\0') {
135 return game
.server
.meta_info
.user_message
;
141 /*************************************************************************
142 Update meta message. Set it to user meta message, if it is available.
143 Otherwise use provided message.
144 It is ok to call this with NULL message. Then it only replaces current
145 meta message with user meta message if available.
146 *************************************************************************/
147 void maybe_automatic_meta_message(const char *automatic
)
149 const char *user_message
;
151 user_message
= get_user_meta_message_string();
153 if (user_message
== NULL
) {
154 /* No user message */
155 if (automatic
!= NULL
) {
156 set_meta_message_string(automatic
);
161 set_meta_message_string(user_message
);
164 /*************************************************************************
165 set the metaserver patches string
166 *************************************************************************/
167 void set_meta_patches_string(const char *string
)
169 sz_strlcpy(meta_patches
, string
);
172 /*************************************************************************
173 set the metaserver message string
174 *************************************************************************/
175 void set_meta_message_string(const char *string
)
177 sz_strlcpy(meta_message
, string
);
180 /*************************************************************************
181 set user defined metaserver message string
182 *************************************************************************/
183 void set_user_meta_message_string(const char *string
)
185 if (string
!= NULL
&& string
[0] != '\0') {
186 sz_strlcpy(game
.server
.meta_info
.user_message
, string
);
187 set_meta_message_string(string
);
189 /* Remove user meta message. We will use automatic messages instead */
190 game
.server
.meta_info
.user_message
[0] = '\0';
191 set_meta_message_string(default_meta_message_string());
195 /*************************************************************************
196 Return string describing both metaserver name and port.
197 *************************************************************************/
198 char *meta_addr_port(void)
200 return srvarg
.metaserver_addr
;
203 /*************************************************************************
204 we couldn't find or connect to the metaserver.
205 *************************************************************************/
206 static void metaserver_failed(void)
208 if (!persistent_meta_connection
) {
209 con_puts(C_METAERROR
, _("Not reporting to the metaserver in this game."));
214 con_puts(C_METAERROR
, _("Metaserver connection currently failing."));
219 /****************************************************************************
220 Insert a setting in the metaserver message. Return TRUE if it succeded.
221 ****************************************************************************/
222 static inline bool meta_insert_setting(struct netfile_post
*post
,
223 const char *set_name
)
225 const struct setting
*pset
= setting_by_name(set_name
);
228 fc_assert_ret_val_msg(NULL
!= pset
, FALSE
,
229 "Setting \"%s\" not found!", set_name
);
230 netfile_add_form_str(post
, "vn[]", setting_name(pset
));
231 netfile_add_form_str(post
, "vv[]",
232 setting_value_name(pset
, FALSE
, buf
, sizeof(buf
)));
236 /*************************************************************************
237 Send POST to metaserver. This runs in its own thread.
238 *************************************************************************/
239 static void send_metaserver_post(void *arg
)
241 struct netfile_post
*post
= (struct netfile_post
*) arg
;
244 if (srvarg
.bind_meta_addr
!= NULL
) {
245 addr
= srvarg
.bind_meta_addr
;
247 addr
= srvarg
.bind_addr
;
250 if (!netfile_send_post(srvarg
.metaserver_addr
, post
, NULL
, NULL
, addr
)) {
251 con_puts(C_METAERROR
, _("Error connecting to metaserver"));
255 netfile_close_post(post
);
258 /*************************************************************************
259 construct the POST message and send info to metaserver.
260 *************************************************************************/
261 static bool send_to_metaserver(enum meta_flag flag
)
268 struct netfile_post
*post
;
270 switch(server_state()) {
272 sz_strlcpy(state
, "Pregame");
275 sz_strlcpy(state
, "Running");
278 sz_strlcpy(state
, "Game Ended");
283 if (srvarg
.identity_name
[0] != '\0') {
284 sz_strlcpy(host
, srvarg
.identity_name
);
285 } else if (fc_gethostname(host
, sizeof(host
)) != 0) {
286 sz_strlcpy(host
, "unknown");
289 if (game
.control
.version
[0] != '\0') {
290 fc_snprintf(rs
, sizeof(rs
), "%s %s", game
.control
.name
, game
.control
.version
);
292 sz_strlcpy(rs
, game
.control
.name
);
295 /* Freed in metaserver thread function send_metaserver_post() */
296 post
= netfile_start_post();
298 netfile_add_form_str(post
, "host", host
);
299 netfile_add_form_int(post
, "port", srvarg
.port
);
300 netfile_add_form_str(post
, "state", state
);
301 netfile_add_form_str(post
, "ruleset", rs
);
303 if (flag
== META_GOODBYE
) {
304 netfile_add_form_int(post
, "bye", 1);
306 const char *srvtype
= get_meta_type_string();
308 if (srvtype
!= NULL
) {
309 netfile_add_form_str(post
, "type", srvtype
);
311 netfile_add_form_str(post
, "version", VERSION_STRING
);
312 netfile_add_form_str(post
, "patches",
313 get_meta_patches_string());
314 netfile_add_form_str(post
, "capability", our_capability
);
316 netfile_add_form_str(post
, "serverid", srvarg
.serverid
);
317 netfile_add_form_str(post
, "message",
318 get_meta_message_string());
320 /* NOTE: send info for ALL players or none at all. */
321 if (normal_player_count() == 0) {
322 netfile_add_form_int(post
, "dropplrs", 1);
324 players
= 0; /* a counter for players_available */
327 players_iterate(plr
) {
328 bool is_player_available
= TRUE
;
330 struct connection
*pconn
= conn_by_user(plr
->username
);
332 if (!plr
->is_alive
) {
333 sz_strlcpy(type
, "Dead");
334 } else if (is_barbarian(plr
)) {
335 sz_strlcpy(type
, "Barbarian");
336 } else if (is_ai(plr
)) {
337 sz_strlcpy(type
, "A.I.");
338 } else if (is_human(plr
)) {
339 sz_strlcpy(type
, "Human");
341 sz_strlcpy(type
, "-");
344 netfile_add_form_str(post
, "plu[]", plr
->username
);
345 netfile_add_form_str(post
, "plt[]", type
);
346 netfile_add_form_str(post
, "pll[]", player_name(plr
));
347 netfile_add_form_str(post
, "pln[]",
348 plr
->nation
!= NO_NATION_SELECTED
349 ? nation_plural_for_player(plr
)
351 netfile_add_form_str(post
, "plf[]",
352 plr
->nation
!= NO_NATION_SELECTED
353 ? nation_of_player(plr
)->flag_graphic_str
355 netfile_add_form_str(post
, "plh[]",
356 pconn
? pconn
->addr
: "");
358 /* is this player available to take?
359 * TODO: there's some duplication here with
360 * stdinhand.c:is_allowed_to_take() */
361 if (is_barbarian(plr
) && !strchr(game
.server
.allow_take
, 'b')) {
362 is_player_available
= FALSE
;
363 } else if (!plr
->is_alive
&& !strchr(game
.server
.allow_take
, 'd')) {
364 is_player_available
= FALSE
;
365 } else if (is_ai(plr
)
366 && !strchr(game
.server
.allow_take
,
367 (game
.info
.is_new_game
? 'A' : 'a'))) {
368 is_player_available
= FALSE
;
369 } else if (is_human(plr
)
370 && !strchr(game
.server
.allow_take
,
371 (game
.info
.is_new_game
? 'H' : 'h'))) {
372 is_player_available
= FALSE
;
376 is_player_available
= FALSE
;
379 if (is_player_available
) {
383 if (is_human(plr
) && plr
->is_alive
) {
386 } players_iterate_end
;
388 /* send the number of available players. */
389 netfile_add_form_int(post
, "available", players
);
390 netfile_add_form_int(post
, "humans", humans
);
393 /* Send some variables: should be listed in inverted order? */
395 static const char *settings
[] = {
396 "timeout", "endturn", "minplayers", "maxplayers",
397 "aifill", "allowtake", "generator"
401 for (i
= 0; i
< ARRAY_SIZE(settings
); i
++) {
402 meta_insert_setting(post
, settings
[i
]);
405 /* HACK: send the most determinant setting for the map size. */
406 switch (wld
.map
.server
.mapsize
) {
407 case MAPSIZE_FULLSIZE
:
408 meta_insert_setting(post
, "size");
411 meta_insert_setting(post
, "tilesperplayer");
414 meta_insert_setting(post
, "xsize");
415 meta_insert_setting(post
, "ysize");
421 netfile_add_form_str(post
, "vn[]", "turn");
422 netfile_add_form_int(post
, "vv[]", game
.info
.turn
);
423 netfile_add_form_str(post
, "vn[]", "year");
425 if (server_state() != S_S_INITIAL
) {
426 netfile_add_form_int(post
, "vv[]", game
.info
.year
);
428 netfile_add_form_str(post
, "vv[]", "Calendar not set up");
432 if (meta_srv_thread
!= NULL
) {
433 /* Previously started thread */
434 fc_thread_wait(meta_srv_thread
);
436 meta_srv_thread
= fc_malloc(sizeof(meta_srv_thread
));
439 /* Send POST in new thread */
440 fc_thread_start(meta_srv_thread
, &send_metaserver_post
, post
);
445 /*************************************************************************
446 Stop sending updates to metaserver
447 *************************************************************************/
448 void server_close_meta(void)
450 server_is_open
= FALSE
;
451 persistent_meta_connection
= FALSE
;
454 /*************************************************************************
455 Lookup the correct address for the metaserver.
456 *************************************************************************/
457 bool server_open_meta(bool persistent
)
459 if (meta_patches
[0] == '\0') {
460 set_meta_patches_string(default_meta_patches_string());
462 if (meta_message
[0] == '\0') {
463 set_meta_message_string(default_meta_message_string());
466 server_is_open
= TRUE
;
467 persistent_meta_connection
= persistent
;
473 /**************************************************************************
474 Are we sending info to the metaserver?
475 **************************************************************************/
476 bool is_metaserver_open(void)
478 return server_is_open
;
481 /**************************************************************************
482 Control when we send info to the metaserver.
483 **************************************************************************/
484 bool send_server_info_to_metaserver(enum meta_flag flag
)
486 static struct timer
*last_send_timer
= NULL
;
487 static bool want_update
;
489 if (!server_is_open
) {
493 /* Persistent connection temporary failures handling */
494 if (meta_retry_wait
> 0) {
495 if (meta_retry_wait
++ > 5) {
502 /* if we're bidding farewell, ignore all timers */
503 if (flag
== META_GOODBYE
) {
504 if (last_send_timer
) {
505 timer_destroy(last_send_timer
);
506 last_send_timer
= NULL
;
508 send_to_metaserver(flag
);
510 /* Wait metaserver thread to finish */
511 fc_thread_wait(meta_srv_thread
);
512 free(meta_srv_thread
);
513 meta_srv_thread
= NULL
;
518 /* don't allow the user to spam the metaserver with updates */
519 if (last_send_timer
&& (timer_read_seconds(last_send_timer
)
520 < METASERVER_MIN_UPDATE_INTERVAL
)) {
521 if (flag
== META_INFO
) {
522 want_update
= TRUE
; /* we couldn't update now, but update a.s.a.p. */
527 /* if we're asking for a refresh, only do so if
528 * we've exceeded the refresh interval */
529 if ((flag
== META_REFRESH
) && !want_update
&& last_send_timer
530 && (timer_read_seconds(last_send_timer
) < METASERVER_REFRESH_INTERVAL
)) {
534 /* start a new timer if we haven't already */
535 if (!last_send_timer
) {
536 last_send_timer
= timer_new(TIMER_USER
, TIMER_ACTIVE
);
539 timer_clear(last_send_timer
);
540 timer_start(last_send_timer
);
543 return send_to_metaserver(flag
);