1 /**********************************************************************
2 Freeciv - Copyright (C) 2004 - The Freeciv Project
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 ***********************************************************************/
14 #include <fc_config.h>
19 #include <signal.h> /* SIGTERM and kill */
27 #ifdef HAVE_SYS_TYPES_H
28 #include <sys/types.h> /* fchmod */
31 #ifdef HAVE_SYS_STAT_H
32 #include <sys/stat.h> /* fchmod */
35 #ifdef HAVE_SYS_WAIT_H
40 #include "capability.h"
53 #include "client_main.h"
55 #include "clinet.h" /* connect_to_server() */
56 #include "packhand_gen.h"
58 #include "chatline_common.h"
59 #include "connectdlg_g.h"
60 #include "connectdlg_common.h"
63 #define WAIT_BETWEEN_TRIES 100000 /* usecs */
64 #define NUMBER_OF_TRIES 500
67 /* FIXME: this is referenced directly in gui-win32/connectdlg.c. */
68 HANDLE server_process
= INVALID_HANDLE_VALUE
;
69 HANDLE loghandle
= INVALID_HANDLE_VALUE
;
71 static pid_t server_pid
= - 1;
74 static char challenge_fullname
[MAX_LEN_PATH
];
75 static bool client_has_hack
= FALSE
;
77 int internal_server_port
;
79 /**************************************************************************
80 The general chain of events:
82 Two distinct paths are taken depending on the choice of mode:
84 if the user selects the multi- player mode, then a packet_req_join_game
85 packet is sent to the server. It is either successful or not. The end.
87 If the user selects a single- player mode (either a new game or a save game)
89 1. the packet_req_join_game is sent.
90 2. on receipt, if we can join, then a challenge packet is sent to the
91 server, so we can get hack level control.
92 3. if we can't get hack, then we get dumped to multi- player mode. If
94 a. for a new game, we send a series of packet_generic_message packets
95 with commands to start the game.
96 b. for a saved game, we send the load command with a
97 packet_generic_message, then we send a PACKET_PLAYER_LIST_REQUEST.
98 the response to this request will tell us if the game was loaded or
99 not. if not, then we send another load command. if so, then we send
100 a series of packet_generic_message packets with commands to start
102 **************************************************************************/
104 /**************************************************************************
105 Tests if the client has started the server.
106 **************************************************************************/
107 bool is_server_running()
110 return (server_process
!= INVALID_HANDLE_VALUE
);
112 return (server_pid
> 0);
116 /**************************************************************************
117 Returns TRUE if the client has hack access.
118 **************************************************************************/
119 bool can_client_access_hack(void)
121 return client_has_hack
;
124 /****************************************************************************
125 Kills the server if the client has started it.
127 If the 'force' parameter is unset, we just do a /quit. If it's set, then
128 we'll send a signal to the server to kill it (use this when the socket
129 is disconnected already).
130 ****************************************************************************/
131 void client_kill_server(bool force
)
133 if (is_server_running()) {
134 if (client
.conn
.used
&& client_has_hack
) {
135 /* This does a "soft" shutdown of the server by sending a /quit.
137 * This is useful when closing the client or disconnecting because it
138 * doesn't kill the server prematurely. In particular, killing the
139 * server in the middle of a save can have disastrous results. This
140 * method tells the server to quit on its own. This is safer from a
141 * game perspective, but more dangerous because if the kill fails the
142 * server will be left running.
144 * Another potential problem is because this function is called atexit
145 * it could potentially be called when we're connected to an unowned
146 * server. In this case we don't want to kill it. */
149 server_process
= INVALID_HANDLE_VALUE
;
150 loghandle
= INVALID_HANDLE_VALUE
;
155 /* Either we already disconnected, or we didn't get control of the
156 * server. In either case, the only thing to do is a "hard" kill of
159 TerminateProcess(server_process
, 0);
160 CloseHandle(server_process
);
161 if (loghandle
!= INVALID_HANDLE_VALUE
) {
162 CloseHandle(loghandle
);
164 server_process
= INVALID_HANDLE_VALUE
;
165 loghandle
= INVALID_HANDLE_VALUE
;
166 #elif HAVE_WORKING_FORK
167 kill(server_pid
, SIGTERM
);
168 waitpid(server_pid
, NULL
, WUNTRACED
);
170 #endif /* WIN32_NATIVE || HAVE_WORKING_FORK */
173 client_has_hack
= FALSE
;
176 /****************************************************************
177 forks a server if it can. returns FALSE is we find we couldn't start
179 *****************************************************************/
180 bool client_start_server(void)
182 #if !defined(HAVE_WORKING_FORK) && !defined(WIN32_NATIVE)
183 /* Can't do much without fork */
185 #else /* HAVE_WORKING_FORK || WIN32_NATIVE */
187 int connect_tries
= 0;
190 PROCESS_INFORMATION pi
;
192 char savesdir
[MAX_LEN_PATH
];
193 char scensdir
[MAX_LEN_PATH
];
199 char logcmdline
[512];
200 char scriptcmdline
[512];
201 char savescmdline
[512];
202 char scenscmdline
[512];
203 # endif /* WIN32_NATIVE */
206 /* We want port that is free in IPv4 even if we (the client) have
207 * IPv6 support. In the unlikely case that local server is IPv4-only
208 * (meaning that it has to be from different build than client) we
209 * have to give port that it can use. IPv6-enabled client would first
210 * try same port in IPv6 and if that fails, fallback to IPv4 too. */
211 enum fc_addr_family family
= FC_ADDR_IPV4
;
213 enum fc_addr_family family
= FC_ADDR_IPV4
;
214 #endif /* IPV6_SUPPORT */
216 /* only one server (forked from this client) shall be running at a time */
217 /* This also resets client_has_hack. */
218 client_kill_server(TRUE
);
220 output_window_append(ftc_client
, _("Starting local server..."));
222 /* find a free port */
223 /* Mitigate the risk of ending up with the port already
224 * used by standalone server on Windows where this is known to be buggy
225 * by not starting from DEFAULT_SOCK_PORT but from one higher. */
226 internal_server_port
= find_next_free_port(DEFAULT_SOCK_PORT
+ 1,
227 family
, "localhost");
229 if (internal_server_port
< 0) {
230 output_window_append(ftc_client
, _("Couldn't start the server."));
231 output_window_append(ftc_client
,
232 _("You'll have to start one manually. Sorry..."));
236 # ifdef HAVE_WORKING_FORK
239 if (server_pid
== 0) {
241 const int max_nargs
= 18;
242 char *argv
[max_nargs
+ 1], port_buf
[32];
244 /* inside the child */
246 /* Set up the command-line parameters. */
247 fc_snprintf(port_buf
, sizeof(port_buf
), "%d", internal_server_port
);
248 argv
[argc
++] = "freeciv-server";
250 argv
[argc
++] = port_buf
;
251 argv
[argc
++] = "--bind";
252 argv
[argc
++] = "localhost";
256 argv
[argc
++] = "--saves";
257 argv
[argc
++] = "~/.freeciv/saves";
258 argv
[argc
++] = "--scenarios";
259 argv
[argc
++] = "~/.freeciv/scenarios";
261 argv
[argc
++] = "none";
263 argv
[argc
++] = "--debug";
265 argv
[argc
++] = "--log";
266 argv
[argc
++] = logfile
;
269 argv
[argc
++] = "--read";
270 argv
[argc
++] = scriptfile
;
273 fc_assert(argc
<= max_nargs
);
275 /* avoid terminal spam, but still make server output available */
279 /* FIXME: include the port to avoid duplication? */
281 fd
= open(logfile
, O_WRONLY
| O_CREAT
| O_APPEND
, 0644);
292 /* If it's still attatched to our terminal, things get messed up,
293 but freeciv-server needs *something* */
295 fd
= open("/dev/null", O_RDONLY
);
300 /* these won't return on success */
302 /* Search under current directory (what ever that happens to be)
303 * only in debug builds. This allows running freeciv directly from build
304 * tree, but could be considered security risk in release builds. */
305 execvp("./fcser", argv
);
306 execvp("./server/freeciv-server", argv
);
308 execvp(BINDIR
"/freeciv-server", argv
);
309 execvp("freeciv-server", argv
);
311 /* This line is only reached if freeciv-server cannot be started,
312 * so we kill the forked process.
313 * Calling exit here is dangerous due to X11 problems (async replies) */
316 # else /* HAVE_WORKING_FORK */
319 loghandle
= CreateFile(logfile
, GENERIC_WRITE
,
320 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
322 OPEN_ALWAYS
, 0, NULL
);
325 ZeroMemory(&si
, sizeof(si
));
327 si
.hStdOutput
= loghandle
;
328 si
.hStdInput
= INVALID_HANDLE_VALUE
;
329 si
.hStdError
= loghandle
;
330 si
.dwFlags
= STARTF_USESTDHANDLES
;
332 /* Set up the command-line parameters. */
334 scriptcmdline
[0] = 0;
336 /* the server expects command line arguments to be in local encoding */
338 char *logfile_in_local_encoding
=
339 internal_to_local_string_malloc(logfile
);
341 fc_snprintf(logcmdline
, sizeof(logcmdline
), " --debug 3 --log %s",
342 logfile_in_local_encoding
);
343 free(logfile_in_local_encoding
);
346 char *scriptfile_in_local_encoding
=
347 internal_to_local_string_malloc(scriptfile
);
349 fc_snprintf(scriptcmdline
, sizeof(scriptcmdline
), " --read %s",
350 scriptfile_in_local_encoding
);
351 free(scriptfile_in_local_encoding
);
354 interpret_tilde(savesdir
, sizeof(savesdir
), "~/.freeciv/saves");
355 internal_to_local_string_buffer(savesdir
, savescmdline
, sizeof(savescmdline
));
357 interpret_tilde(scensdir
, sizeof(scensdir
), "~/.freeciv/scenarios");
358 internal_to_local_string_buffer(scensdir
, scenscmdline
, sizeof(scenscmdline
));
360 fc_snprintf(options
, sizeof(options
),
361 "-p %d --bind localhost -q 1 -e%s%s --saves \"%s\" "
362 "--scenarios \"%s\" -A none",
363 internal_server_port
, logcmdline
, scriptcmdline
, savescmdline
,
365 fc_snprintf(cmdline1
, sizeof(cmdline1
), "./fcser %s", options
);
366 fc_snprintf(cmdline2
, sizeof(cmdline2
),
367 "./server/freeciv-server %s", options
);
368 fc_snprintf(cmdline3
, sizeof(cmdline3
),
369 BINDIR
"/freeciv-server %s", options
);
370 fc_snprintf(cmdline4
, sizeof(cmdline4
),
371 "freeciv-server %s", options
);
375 !CreateProcess(NULL
, cmdline1
, NULL
, NULL
, TRUE
,
376 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
377 NULL
, NULL
, &si
, &pi
)
378 && !CreateProcess(NULL
, cmdline2
, NULL
, NULL
, TRUE
,
379 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
380 NULL
, NULL
, &si
, &pi
)
383 !CreateProcess(NULL
, cmdline3
, NULL
, NULL
, TRUE
,
384 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
385 NULL
, NULL
, &si
, &pi
)
386 && !CreateProcess(NULL
, cmdline4
, NULL
, NULL
, TRUE
,
387 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
388 NULL
, NULL
, &si
, &pi
)) {
389 output_window_append(ftc_client
, _("Couldn't start the server."));
390 output_window_append(ftc_client
,
391 _("You'll have to start one manually. Sorry..."));
395 server_process
= pi
.hProcess
;
397 # endif /* WIN32_NATIVE */
398 # endif /* HAVE_WORKING_FORK */
400 /* a reasonable number of tries */
401 while (connect_to_server(user_name
, "localhost", internal_server_port
,
402 buf
, sizeof(buf
)) == -1) {
403 fc_usleep(WAIT_BETWEEN_TRIES
);
404 #ifdef HAVE_WORKING_FORK
406 if (waitpid(server_pid
, NULL
, WNOHANG
) != 0) {
409 #endif /* WIN32_NATIVE */
410 #endif /* HAVE_WORKING_FORK */
411 if (connect_tries
++ > NUMBER_OF_TRIES
) {
416 /* weird, but could happen, if server doesn't support new startup stuff
417 * capabilities won't help us here... */
418 if (!client
.conn
.used
) {
419 /* possible that server is still running. kill it */
420 client_kill_server(TRUE
);
422 output_window_append(ftc_client
, _("Couldn't connect to the server."));
423 output_window_append(ftc_client
,
424 _("We probably couldn't start it from here."));
425 output_window_append(ftc_client
,
426 _("You'll have to start one manually. Sorry..."));
430 /* We set the topology to match the view.
432 * When a typical player launches a game, he wants the map orientation to
433 * match the tileset orientation. So if you use an isometric tileset you
434 * get an iso-map and for a classic tileset you get a classic map. In
435 * both cases the map wraps in the X direction by default.
437 * This works with hex maps too now. A hex map always has
438 * tileset_is_isometric(tileset) return TRUE. An iso-hex map has
439 * tileset_hex_height(tileset) != 0, while a non-iso hex map
440 * has tileset_hex_width(tileset) != 0.
442 * Setting the option here is a bit of a hack, but so long as the client
443 * has sufficient permissions to do so (it doesn't have HACK access yet) it
444 * is safe enough. Note that if you load a savegame the topology will be
445 * set but then overwritten during the load.
447 * Don't send it now, it will be sent to the server when receiving the
448 * server setting infos. */
452 fc_strlcpy(buf
, "WRAPX", sizeof(buf
));
453 if (tileset_is_isometric(tileset
) && 0 == tileset_hex_height(tileset
)) {
454 fc_strlcat(buf
, "|ISO", sizeof(buf
));
456 if (0 < tileset_hex_width(tileset
) || 0 < tileset_hex_height(tileset
)) {
457 fc_strlcat(buf
, "|HEX", sizeof(buf
));
459 desired_settable_option_update("topology", buf
, FALSE
);
463 #endif /* HAVE_WORKING_FORK || WIN32_NATIVE */
466 /*************************************************************************
467 generate a random string.
468 *************************************************************************/
469 static void randomize_string(char *str
, size_t n
)
472 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
475 for (i
= 0; i
< n
- 1; i
++) {
476 str
[i
] = chars
[fc_rand(sizeof(chars
) - 1)];
481 /****************************************************************
482 if the client is capable of 'wanting hack', then the server will
483 send the client a filename in the packet_join_game_reply packet.
485 this function creates the file with a suitably random string in it
486 and then sends the string to the server. If the server can open
487 and read the string, then the client is given hack access.
488 *****************************************************************/
489 void send_client_wants_hack(const char *filename
)
491 if (filename
[0] != '\0') {
492 struct packet_single_want_hack_req req
;
493 struct section_file
*file
;
495 if (!is_safe_filename(filename
)) {
499 /* get the full filename path */
500 interpret_tilde(challenge_fullname
, sizeof(challenge_fullname
),
502 make_dir(challenge_fullname
);
504 sz_strlcat(challenge_fullname
, filename
);
506 /* generate an authentication token */
507 randomize_string(req
.token
, sizeof(req
.token
));
509 file
= secfile_new(FALSE
);
510 secfile_insert_str(file
, req
.token
, "challenge.token");
511 if (!secfile_save(file
, challenge_fullname
, 0, FZ_PLAIN
)) {
512 log_error("Couldn't write token to temporary file: %s",
515 secfile_destroy(file
);
517 /* tell the server what we put into the file */
518 send_packet_single_want_hack_req(&client
.conn
, &req
);
522 /****************************************************************
523 handle response (by the server) if the client has got hack or not.
524 *****************************************************************/
525 void handle_single_want_hack_reply(bool you_have_hack
)
527 /* remove challenge file */
528 if (challenge_fullname
[0] != '\0') {
529 if (fc_remove(challenge_fullname
) == -1) {
530 log_error("Couldn't remove temporary file: %s", challenge_fullname
);
532 challenge_fullname
[0] = '\0';
536 output_window_append(ftc_client
,
537 _("Established control over the server. "
538 "You have command access level 'hack'."));
539 client_has_hack
= TRUE
;
540 } else if (is_server_running()) {
541 /* only output this if we started the server and we NEED hack */
542 output_window_append(ftc_client
,
543 _("Failed to obtain the required access "
544 "level to take control of the server. "
545 "Attempting to shut down server."));
546 client_kill_server(TRUE
);
550 /****************************************************************
551 send server command to save game.
552 *****************************************************************/
553 void send_save_game(const char *filename
)
556 send_chat_printf("/save %s", filename
);
562 /**************************************************************************
563 Handle the list of rulesets sent by the server.
564 **************************************************************************/
565 void handle_ruleset_choices(const struct packet_ruleset_choices
*packet
)
567 char *rulesets
[packet
->ruleset_count
];
569 size_t suf_len
= strlen(RULESET_SUFFIX
);
571 for (i
= 0; i
< packet
->ruleset_count
; i
++) {
572 size_t len
= strlen(packet
->rulesets
[i
]);
574 rulesets
[i
] = fc_strdup(packet
->rulesets
[i
]);
577 && strcmp(rulesets
[i
] + len
- suf_len
, RULESET_SUFFIX
) == 0) {
578 rulesets
[i
][len
- suf_len
] = '\0';
581 gui_set_rulesets(packet
->ruleset_count
, rulesets
);
583 for (i
= 0; i
< packet
->ruleset_count
; i
++) {
588 /**************************************************************************
589 Called by the GUI code when the user sets the ruleset. The ruleset
590 passed in here should match one of the strings given to gui_set_rulesets.
591 **************************************************************************/
592 void set_ruleset(const char *ruleset
)
596 fc_snprintf(buf
, sizeof(buf
), "/read %s%s", ruleset
, RULESET_SUFFIX
);
597 log_debug("Executing '%s'", buf
);