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>
17 #include "fc_prehdrs.h"
21 #include <signal.h> /* SIGTERM and kill */
29 #ifdef FREECIV_HAVE_SYS_TYPES_H
30 #include <sys/types.h> /* fchmod */
33 #ifdef HAVE_SYS_STAT_H
34 #include <sys/stat.h> /* fchmod */
37 #ifdef HAVE_SYS_WAIT_H
43 #include "capability.h"
44 #include "deprecations.h"
57 #include "client_main.h"
59 #include "clinet.h" /* connect_to_server() */
60 #include "packhand_gen.h"
62 #include "chatline_common.h"
63 #include "connectdlg_g.h"
64 #include "connectdlg_common.h"
67 #define WAIT_BETWEEN_TRIES 100000 /* usecs */
68 #define NUMBER_OF_TRIES 500
70 #if defined(HAVE_WORKING_FORK) && !defined(WIN32_NATIVE)
71 /* We are yet to see WIN32_NATIVE setup where even HAVE_WORKING_FORK would
72 * mean fork() that actually works for us. */
73 #define HAVE_USABLE_FORK
76 #ifdef HAVE_USABLE_FORK
77 static pid_t server_pid
= -1;
78 #elif FREECIV_MSWINDOWS
79 HANDLE server_process
= INVALID_HANDLE_VALUE
;
80 HANDLE loghandle
= INVALID_HANDLE_VALUE
;
81 #endif /* HAVE_USABLE_FORK || FREECIV_MSWINDOWS */
82 bool server_quitting
= FALSE
;
84 static char challenge_fullname
[MAX_LEN_PATH
];
85 static bool client_has_hack
= FALSE
;
87 int internal_server_port
;
89 /**************************************************************************
90 The general chain of events:
92 Two distinct paths are taken depending on the choice of mode:
94 if the user selects the multi- player mode, then a packet_req_join_game
95 packet is sent to the server. It is either successful or not. The end.
97 If the user selects a single- player mode (either a new game or a save game)
99 1. the packet_req_join_game is sent.
100 2. on receipt, if we can join, then a challenge packet is sent to the
101 server, so we can get hack level control.
102 3. if we can't get hack, then we get dumped to multi- player mode. If
104 a. for a new game, we send a series of packet_generic_message packets
105 with commands to start the game.
106 b. for a saved game, we send the load command with a
107 packet_generic_message, then we send a PACKET_PLAYER_LIST_REQUEST.
108 the response to this request will tell us if the game was loaded or
109 not. if not, then we send another load command. if so, then we send
110 a series of packet_generic_message packets with commands to start
112 **************************************************************************/
114 /**************************************************************************
115 Tests if the client has started the server.
116 **************************************************************************/
117 bool is_server_running(void)
119 if (server_quitting
) {
122 #ifdef HAVE_USABLE_FORK
123 return (server_pid
> 0);
124 #elif FREECIV_MSWINDOWS
125 return (server_process
!= INVALID_HANDLE_VALUE
);
127 return FALSE
; /* We've been unable to start one! */
131 /**************************************************************************
132 Returns TRUE if the client has hack access.
133 **************************************************************************/
134 bool can_client_access_hack(void)
136 return client_has_hack
;
139 /****************************************************************************
140 Kills the server if the client has started it.
142 If the 'force' parameter is unset, we just do a /quit. If it's set, then
143 we'll send a signal to the server to kill it (use this when the socket
144 is disconnected already).
145 ****************************************************************************/
146 void client_kill_server(bool force
)
148 #ifdef HAVE_USABLE_FORK
149 if (server_quitting
&& server_pid
> 0) {
150 /* Already asked to /quit.
151 * If it didn't do that, kill it. */
152 if (waitpid(server_pid
, NULL
, WUNTRACED
) <= 0) {
153 kill(server_pid
, SIGTERM
);
154 waitpid(server_pid
, NULL
, WUNTRACED
);
157 server_quitting
= FALSE
;
159 #elif FREECIV_MSWINDOWS
160 if (server_quitting
&& server_process
!= INVALID_HANDLE_VALUE
) {
161 /* Already asked to /quit.
162 * If it didn't do that, kill it. */
163 TerminateProcess(server_process
, 0);
164 CloseHandle(server_process
);
165 if (loghandle
!= INVALID_HANDLE_VALUE
) {
166 CloseHandle(loghandle
);
168 server_process
= INVALID_HANDLE_VALUE
;
169 loghandle
= INVALID_HANDLE_VALUE
;
170 server_quitting
= FALSE
;
172 #endif /* FREECIV_MSWINDOWS || HAVE_USABLE_FORK */
174 if (is_server_running()) {
175 if (client
.conn
.used
&& client_has_hack
) {
176 /* This does a "soft" shutdown of the server by sending a /quit.
178 * This is useful when closing the client or disconnecting because it
179 * doesn't kill the server prematurely. In particular, killing the
180 * server in the middle of a save can have disastrous results. This
181 * method tells the server to quit on its own. This is safer from a
182 * game perspective, but more dangerous because if the kill fails the
183 * server will be left running.
185 * Another potential problem is because this function is called atexit
186 * it could potentially be called when we're connected to an unowned
187 * server. In this case we don't want to kill it. */
189 server_quitting
= TRUE
;
191 /* Either we already disconnected, or we didn't get control of the
192 * server. In either case, the only thing to do is a "hard" kill of
194 #ifdef HAVE_USABLE_FORK
195 kill(server_pid
, SIGTERM
);
196 waitpid(server_pid
, NULL
, WUNTRACED
);
198 #elif FREECIV_MSWINDOWS
199 TerminateProcess(server_process
, 0);
200 CloseHandle(server_process
);
201 if (loghandle
!= INVALID_HANDLE_VALUE
) {
202 CloseHandle(loghandle
);
204 server_process
= INVALID_HANDLE_VALUE
;
205 loghandle
= INVALID_HANDLE_VALUE
;
206 #endif /* FREECIV_MSWINDOWS || HAVE_USABLE_FORK */
207 server_quitting
= FALSE
;
210 client_has_hack
= FALSE
;
213 /****************************************************************
214 Forks a server if it can. Returns FALSE if we find we
215 couldn't start the server.
216 *****************************************************************/
217 bool client_start_server(void)
219 #if !defined(HAVE_USABLE_FORK) && !defined(WIN32_NATIVE)
220 /* Can't do much without fork */
222 #else /* HAVE_USABLE_FORK || WIN32_NATIVE */
224 int connect_tries
= 0;
225 #if !defined(HAVE_USABLE_FORK)
226 /* Above also implies that this is WIN32_NATIVE ->
227 * Win32 that can't use fork() */
229 PROCESS_INFORMATION pi
;
231 char savesdir
[MAX_LEN_PATH
];
232 char scensdir
[MAX_LEN_PATH
];
238 #endif /* FREECIV_DEBUG */
241 char logcmdline
[512];
242 char scriptcmdline
[512];
243 char savefilecmdline
[512];
244 char savescmdline
[512];
245 char scenscmdline
[512];
246 #endif /* !HAVE_USABLE_FORK -> WIN32_NATIVE */
248 #ifdef FREECIV_IPV6_SUPPORT
249 enum fc_addr_family family
= FC_ADDR_ANY
;
251 enum fc_addr_family family
= FC_ADDR_IPV4
;
252 #endif /* FREECIV_IPV6_SUPPORT */
254 /* only one server (forked from this client) shall be running at a time */
255 /* This also resets client_has_hack. */
256 client_kill_server(TRUE
);
258 output_window_append(ftc_client
, _("Starting local server..."));
260 /* find a free port */
261 /* Mitigate the risk of ending up with the port already
262 * used by standalone server on Windows where this is known to be buggy
263 * by not starting from DEFAULT_SOCK_PORT but from one higher. */
264 internal_server_port
= find_next_free_port(DEFAULT_SOCK_PORT
+ 1,
265 DEFAULT_SOCK_PORT
+ 1 + 10000,
266 family
, "localhost", TRUE
);
268 if (internal_server_port
< 0) {
269 output_window_append(ftc_client
, _("Couldn't start the server."));
270 output_window_append(ftc_client
,
271 _("You'll have to start one manually. Sorry..."));
275 #ifdef HAVE_USABLE_FORK
278 const int max_nargs
= 23;
279 char *argv
[max_nargs
+ 1];
281 char dbg_lvl_buf
[32]; /* Do not move this inside the block where it gets filled,
282 * it's needed via the argv[x] pointer later on, so must
283 * remain in scope. */
285 /* Set up the command-line parameters. */
286 fc_snprintf(port_buf
, sizeof(port_buf
), "%d", internal_server_port
);
287 argv
[argc
++] = "freeciv-server";
289 argv
[argc
++] = port_buf
;
290 argv
[argc
++] = "--bind";
291 argv
[argc
++] = "localhost";
295 argv
[argc
++] = "--saves";
296 argv
[argc
++] = "~" DIR_SEPARATOR
".freeciv" DIR_SEPARATOR
"saves";
297 argv
[argc
++] = "--scenarios";
298 argv
[argc
++] = "~" DIR_SEPARATOR
".freeciv" DIR_SEPARATOR
"scenarios";
300 argv
[argc
++] = "none";
302 enum log_level llvl
= log_get_level();
304 argv
[argc
++] = "--debug";
305 fc_snprintf(dbg_lvl_buf
, sizeof(dbg_lvl_buf
), "%d", llvl
);
306 argv
[argc
++] = dbg_lvl_buf
;
307 argv
[argc
++] = "--log";
308 argv
[argc
++] = logfile
;
311 argv
[argc
++] = "--read";
312 argv
[argc
++] = scriptfile
;
315 argv
[argc
++] = "--file";
316 argv
[argc
++] = savefile
;
318 if (are_deprecation_warnings_enabled()) {
319 argv
[argc
++] = "--warnings";
322 fc_assert(argc
<= max_nargs
);
325 struct astring srv_cmdline_opts
= ASTRING_INIT
;
328 for (i
= 1; i
< argc
; i
++) {
329 astr_add(&srv_cmdline_opts
, i
== 1 ? "%s" : " %s", argv
[i
]);
331 log_verbose("Arguments to spawned server: %s",
332 astr_str(&srv_cmdline_opts
));
333 astr_free(&srv_cmdline_opts
);
337 server_quitting
= FALSE
;
339 if (server_pid
== 0) {
342 /* inside the child */
344 /* avoid terminal spam, but still make server output available */
348 /* FIXME: include the port to avoid duplication? */
350 fd
= open(logfile
, O_WRONLY
| O_CREAT
| O_APPEND
, 0644);
361 /* If it's still attatched to our terminal, things get messed up,
362 but freeciv-server needs *something* */
364 fd
= open("/dev/null", O_RDONLY
);
369 /* these won't return on success */
371 /* Search under current directory (what ever that happens to be)
372 * only in debug builds. This allows running freeciv directly from build
373 * tree, but could be considered security risk in release builds. */
374 execvp("./fcser", argv
);
375 execvp("./server/freeciv-server", argv
);
377 execvp(BINDIR
"/freeciv-server", argv
);
378 execvp("freeciv-server", argv
);
380 /* This line is only reached if freeciv-server cannot be started,
381 * so we kill the forked process.
382 * Calling exit here is dangerous due to X11 problems (async replies) */
386 #else /* HAVE_USABLE_FORK */
389 loghandle
= CreateFile(logfile
, GENERIC_WRITE
,
390 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
392 OPEN_ALWAYS
, 0, NULL
);
395 ZeroMemory(&si
, sizeof(si
));
397 si
.hStdOutput
= loghandle
;
398 si
.hStdInput
= INVALID_HANDLE_VALUE
;
399 si
.hStdError
= loghandle
;
400 si
.dwFlags
= STARTF_USESTDHANDLES
;
402 /* Set up the command-line parameters. */
404 scriptcmdline
[0] = 0;
405 savefilecmdline
[0] = 0;
407 /* the server expects command line arguments to be in local encoding */
409 char *logfile_in_local_encoding
=
410 internal_to_local_string_malloc(logfile
);
411 enum log_level llvl
= log_get_level();
413 fc_snprintf(logcmdline
, sizeof(logcmdline
), " --debug %d --log %s",
414 llvl
, logfile_in_local_encoding
);
415 free(logfile_in_local_encoding
);
418 char *scriptfile_in_local_encoding
=
419 internal_to_local_string_malloc(scriptfile
);
421 fc_snprintf(scriptcmdline
, sizeof(scriptcmdline
), " --read %s",
422 scriptfile_in_local_encoding
);
423 free(scriptfile_in_local_encoding
);
426 char *savefile_in_local_encoding
=
427 internal_to_local_string_malloc(savefile
);
429 fc_snprintf(savefilecmdline
, sizeof(savefilecmdline
), " --file %s",
430 savefile_in_local_encoding
);
431 free(savefile_in_local_encoding
);
434 interpret_tilde(savesdir
, sizeof(savesdir
),
435 "~" DIR_SEPARATOR
".freeciv" DIR_SEPARATOR
"saves");
436 internal_to_local_string_buffer(savesdir
, savescmdline
, sizeof(savescmdline
));
438 interpret_tilde(scensdir
, sizeof(scensdir
),
439 "~" DIR_SEPARATOR
".freeciv" DIR_SEPARATOR
"scenarios");
440 internal_to_local_string_buffer(scensdir
, scenscmdline
, sizeof(scenscmdline
));
442 if (are_deprecation_warnings_enabled()) {
443 depr
= " --warnings";
448 fc_snprintf(options
, sizeof(options
),
449 "-p %d --bind localhost -q 1 -e%s%s%s --saves \"%s\" "
450 "--scenarios \"%s\" -A none %s",
451 internal_server_port
, logcmdline
, scriptcmdline
, savefilecmdline
,
452 savescmdline
, scenscmdline
, depr
);
454 fc_snprintf(cmdline1
, sizeof(cmdline1
), "./fcser %s", options
);
455 fc_snprintf(cmdline2
, sizeof(cmdline2
),
456 "./server/freeciv-server %s", options
);
458 fc_snprintf(cmdline3
, sizeof(cmdline3
),
459 BINDIR
"/freeciv-server %s", options
);
460 fc_snprintf(cmdline4
, sizeof(cmdline4
),
461 "freeciv-server %s", options
);
465 !CreateProcess(NULL
, cmdline1
, NULL
, NULL
, TRUE
,
466 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
467 NULL
, NULL
, &si
, &pi
)
468 && !CreateProcess(NULL
, cmdline2
, NULL
, NULL
, TRUE
,
469 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
470 NULL
, NULL
, &si
, &pi
)
473 !CreateProcess(NULL
, cmdline3
, NULL
, NULL
, TRUE
,
474 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
475 NULL
, NULL
, &si
, &pi
)
476 && !CreateProcess(NULL
, cmdline4
, NULL
, NULL
, TRUE
,
477 DETACHED_PROCESS
| NORMAL_PRIORITY_CLASS
,
478 NULL
, NULL
, &si
, &pi
)) {
479 log_error("Failed to start server process.");
481 log_verbose("Tried with commandline: '%s'", cmdline1
);
482 log_verbose("Tried with commandline: '%s'", cmdline2
);
484 log_verbose("Tried with commandline: '%s'", cmdline3
);
485 log_verbose("Tried with commandline: '%s'", cmdline4
);
486 output_window_append(ftc_client
, _("Couldn't start the server."));
487 output_window_append(ftc_client
,
488 _("You'll have to start one manually. Sorry..."));
492 log_verbose("Arguments to spawned server: %s", options
);
494 server_process
= pi
.hProcess
;
496 #endif /* WIN32_NATIVE */
497 #endif /* HAVE_USABLE_FORK */
499 /* a reasonable number of tries */
500 while (connect_to_server(user_name
, "localhost", internal_server_port
,
501 buf
, sizeof(buf
)) == -1) {
502 fc_usleep(WAIT_BETWEEN_TRIES
);
503 #ifdef HAVE_USABLE_FORK
505 if (waitpid(server_pid
, NULL
, WNOHANG
) != 0) {
508 #endif /* WIN32_NATIVE */
509 #endif /* HAVE_USABLE_FORK */
510 if (connect_tries
++ > NUMBER_OF_TRIES
) {
511 log_error("Last error from connect attempts: '%s'", buf
);
516 /* weird, but could happen, if server doesn't support new startup stuff
517 * capabilities won't help us here... */
518 if (!client
.conn
.used
) {
519 /* possible that server is still running. kill it */
520 client_kill_server(TRUE
);
522 log_error("Failed to connect to spawned server!");
523 output_window_append(ftc_client
, _("Couldn't connect to the server."));
524 output_window_append(ftc_client
,
525 _("We probably couldn't start it from here."));
526 output_window_append(ftc_client
,
527 _("You'll have to start one manually. Sorry..."));
532 /* We set the topology to match the view.
534 * When a typical player launches a game, he wants the map orientation to
535 * match the tileset orientation. So if you use an isometric tileset you
536 * get an iso-map and for a classic tileset you get a classic map. In
537 * both cases the map wraps in the X direction by default.
539 * This works with hex maps too now. A hex map always has
540 * tileset_is_isometric(tileset) return TRUE. An iso-hex map has
541 * tileset_hex_height(tileset) != 0, while a non-iso hex map
542 * has tileset_hex_width(tileset) != 0.
544 * Setting the option here is a bit of a hack, but so long as the client
545 * has sufficient permissions to do so (it doesn't have HACK access yet) it
546 * is safe enough. Note that if you load a savegame the topology will be
547 * set but then overwritten during the load.
549 * Don't send it now, it will be sent to the server when receiving the
550 * server setting infos. */
554 fc_strlcpy(topobuf
, "WRAPX", sizeof(topobuf
));
555 if (tileset_is_isometric(tileset
) && 0 == tileset_hex_height(tileset
)) {
556 fc_strlcat(topobuf
, "|ISO", sizeof(topobuf
));
558 if (0 < tileset_hex_width(tileset
) || 0 < tileset_hex_height(tileset
)) {
559 fc_strlcat(topobuf
, "|HEX", sizeof(topobuf
));
561 desired_settable_option_update("topology", topobuf
, FALSE
);
565 #endif /* HAVE_USABLE_FORK || WIN32_NATIVE */
568 /*************************************************************************
569 generate a random string.
570 *************************************************************************/
571 static void randomize_string(char *str
, size_t n
)
574 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
577 for (i
= 0; i
< n
- 1; i
++) {
578 str
[i
] = chars
[fc_rand(sizeof(chars
) - 1)];
583 /****************************************************************
584 if the client is capable of 'wanting hack', then the server will
585 send the client a filename in the packet_join_game_reply packet.
587 this function creates the file with a suitably random string in it
588 and then sends the string to the server. If the server can open
589 and read the string, then the client is given hack access.
590 *****************************************************************/
591 void send_client_wants_hack(const char *filename
)
593 if (filename
[0] != '\0') {
594 struct packet_single_want_hack_req req
;
595 struct section_file
*file
;
597 if (!is_safe_filename(filename
)) {
601 /* get the full filename path */
602 interpret_tilde(challenge_fullname
, sizeof(challenge_fullname
),
603 "~" DIR_SEPARATOR
".freeciv" DIR_SEPARATOR
);
604 make_dir(challenge_fullname
);
606 sz_strlcat(challenge_fullname
, filename
);
608 /* generate an authentication token */
609 randomize_string(req
.token
, sizeof(req
.token
));
611 file
= secfile_new(FALSE
);
612 secfile_insert_str(file
, req
.token
, "challenge.token");
613 if (!secfile_save(file
, challenge_fullname
, 0, FZ_PLAIN
)) {
614 log_error("Couldn't write token to temporary file: %s",
617 secfile_destroy(file
);
619 /* tell the server what we put into the file */
620 send_packet_single_want_hack_req(&client
.conn
, &req
);
624 /****************************************************************
625 handle response (by the server) if the client has got hack or not.
626 *****************************************************************/
627 void handle_single_want_hack_reply(bool you_have_hack
)
629 /* remove challenge file */
630 if (challenge_fullname
[0] != '\0') {
631 if (fc_remove(challenge_fullname
) == -1) {
632 log_error("Couldn't remove temporary file: %s", challenge_fullname
);
634 challenge_fullname
[0] = '\0';
638 output_window_append(ftc_client
,
639 _("Established control over the server. "
640 "You have command access level 'hack'."));
641 client_has_hack
= TRUE
;
642 } else if (is_server_running()) {
643 /* only output this if we started the server and we NEED hack */
644 output_window_append(ftc_client
,
645 _("Failed to obtain the required access "
646 "level to take control of the server. "
647 "Attempting to shut down server."));
648 client_kill_server(TRUE
);
652 /****************************************************************
653 send server command to save game.
654 *****************************************************************/
655 void send_save_game(const char *filename
)
658 send_chat_printf("/save %s", filename
);
664 /**************************************************************************
665 Handle the list of rulesets sent by the server.
666 **************************************************************************/
667 void handle_ruleset_choices(const struct packet_ruleset_choices
*packet
)
669 char *rulesets
[packet
->ruleset_count
];
671 size_t suf_len
= strlen(RULESET_SUFFIX
);
673 for (i
= 0; i
< packet
->ruleset_count
; i
++) {
674 size_t len
= strlen(packet
->rulesets
[i
]);
676 rulesets
[i
] = fc_strdup(packet
->rulesets
[i
]);
679 && strcmp(rulesets
[i
] + len
- suf_len
, RULESET_SUFFIX
) == 0) {
680 rulesets
[i
][len
- suf_len
] = '\0';
683 set_rulesets(packet
->ruleset_count
, rulesets
);
685 for (i
= 0; i
< packet
->ruleset_count
; i
++) {
690 /**************************************************************************
691 Called by the GUI code when the user sets the ruleset. The ruleset
692 passed in here should match one of the strings given to set_rulesets().
693 **************************************************************************/
694 void set_ruleset(const char *ruleset
)
698 fc_snprintf(buf
, sizeof(buf
), "/read %s%s", ruleset
, RULESET_SUFFIX
);
699 log_debug("Executing '%s'", buf
);