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_set
) {
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 game
.server
.meta_info
.user_message_set
= TRUE
;
188 set_meta_message_string(string
);
190 /* Remove user meta message. We will use automatic messages instead */
191 game
.server
.meta_info
.user_message
[0] = '\0';
192 game
.server
.meta_info
.user_message_set
= FALSE
;
193 set_meta_message_string(default_meta_message_string());
197 /*************************************************************************
198 Return string describing both metaserver name and port.
199 *************************************************************************/
200 char *meta_addr_port(void)
202 return srvarg
.metaserver_addr
;
205 /*************************************************************************
206 we couldn't find or connect to the metaserver.
207 *************************************************************************/
208 static void metaserver_failed(void)
210 if (!persistent_meta_connection
) {
211 con_puts(C_METAERROR
, _("Not reporting to the metaserver in this game."));
216 con_puts(C_METAERROR
, _("Metaserver connection currently failing."));
221 /****************************************************************************
222 Insert a setting in the metaserver message. Return TRUE if it succeded.
223 ****************************************************************************/
224 static inline bool meta_insert_setting(struct netfile_post
*post
,
225 const char *set_name
)
227 const struct setting
*pset
= setting_by_name(set_name
);
230 fc_assert_ret_val_msg(NULL
!= pset
, FALSE
,
231 "Setting \"%s\" not found!", set_name
);
232 netfile_add_form_str(post
, "vn[]", setting_name(pset
));
233 netfile_add_form_str(post
, "vv[]",
234 setting_value_name(pset
, FALSE
, buf
, sizeof(buf
)));
238 /*************************************************************************
239 Send POST to metaserver. This runs in its own thread.
240 *************************************************************************/
241 static void send_metaserver_post(void *arg
)
243 struct netfile_post
*post
= (struct netfile_post
*) arg
;
246 if (srvarg
.bind_meta_addr
!= NULL
) {
247 addr
= srvarg
.bind_meta_addr
;
249 addr
= srvarg
.bind_addr
;
252 if (!netfile_send_post(srvarg
.metaserver_addr
, post
, NULL
, NULL
, addr
)) {
253 con_puts(C_METAERROR
, _("Error connecting to metaserver"));
257 netfile_close_post(post
);
260 /*************************************************************************
261 construct the POST message and send info to metaserver.
262 *************************************************************************/
263 static bool send_to_metaserver(enum meta_flag flag
)
270 struct netfile_post
*post
;
272 switch(server_state()) {
274 sz_strlcpy(state
, "Pregame");
277 sz_strlcpy(state
, "Running");
280 sz_strlcpy(state
, "Game Ended");
285 if (srvarg
.identity_name
[0] != '\0') {
286 sz_strlcpy(host
, srvarg
.identity_name
);
287 } else if (fc_gethostname(host
, sizeof(host
)) != 0) {
288 sz_strlcpy(host
, "unknown");
291 if (game
.control
.version
[0] != '\0') {
292 fc_snprintf(rs
, sizeof(rs
), "%s %s", game
.control
.name
, game
.control
.version
);
294 sz_strlcpy(rs
, game
.control
.name
);
297 /* Freed in metaserver thread function send_metaserver_post() */
298 post
= netfile_start_post();
300 netfile_add_form_str(post
, "host", host
);
301 netfile_add_form_int(post
, "port", srvarg
.port
);
302 netfile_add_form_str(post
, "state", state
);
303 netfile_add_form_str(post
, "ruleset", rs
);
305 if (flag
== META_GOODBYE
) {
306 netfile_add_form_int(post
, "bye", 1);
308 const char *srvtype
= get_meta_type_string();
310 if (srvtype
!= NULL
) {
311 netfile_add_form_str(post
, "type", srvtype
);
313 netfile_add_form_str(post
, "version", VERSION_STRING
);
314 netfile_add_form_str(post
, "patches",
315 get_meta_patches_string());
316 netfile_add_form_str(post
, "capability", our_capability
);
318 netfile_add_form_str(post
, "serverid", srvarg
.serverid
);
319 netfile_add_form_str(post
, "message",
320 get_meta_message_string());
322 /* NOTE: send info for ALL players or none at all. */
323 if (normal_player_count() == 0) {
324 netfile_add_form_int(post
, "dropplrs", 1);
326 players
= 0; /* a counter for players_available */
329 players_iterate(plr
) {
330 bool is_player_available
= TRUE
;
332 struct connection
*pconn
= conn_by_user(plr
->username
);
334 if (!plr
->is_alive
) {
335 sz_strlcpy(type
, "Dead");
336 } else if (is_barbarian(plr
)) {
337 sz_strlcpy(type
, "Barbarian");
338 } else if (plr
->ai_controlled
) {
339 sz_strlcpy(type
, "A.I.");
341 sz_strlcpy(type
, "Human");
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 (plr
->ai_controlled
366 && !strchr(game
.server
.allow_take
,
367 (game
.info
.is_new_game
? 'A' : 'a'))) {
368 is_player_available
= FALSE
;
369 } else if (!plr
->ai_controlled
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 (!plr
->ai_controlled
&& 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 (game
.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
);