Some miscellaneous updates to main help, for new features and otherwise.
[freeciv.git] / client / connectdlg_common.c
blob051cc932a80d6f26e6d4ef50b831d1791fe475cb
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)
6 any later version.
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 ***********************************************************************/
13 #ifdef HAVE_CONFIG_H
14 #include <fc_config.h>
15 #endif
17 #include <fcntl.h>
18 #include <stdio.h>
19 #include <signal.h> /* SIGTERM and kill */
20 #include <string.h>
21 #include <time.h>
23 #ifdef WIN32_NATIVE
24 #include <windows.h>
25 #endif
27 #ifdef HAVE_SYS_TYPES_H
28 #include <sys/types.h> /* fchmod */
29 #endif
31 #ifdef HAVE_SYS_STAT_H
32 #include <sys/stat.h> /* fchmod */
33 #endif
35 #ifdef HAVE_SYS_WAIT_H
36 #include <sys/wait.h>
37 #endif
39 /* utility */
40 #include "capability.h"
41 #include "fciconv.h"
42 #include "fcintl.h"
43 #include "ioz.h"
44 #include "log.h"
45 #include "mem.h"
46 #include "netintf.h"
47 #include "rand.h"
48 #include "registry.h"
49 #include "shared.h"
50 #include "support.h"
52 /* client */
53 #include "client_main.h"
54 #include "climisc.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"
61 #include "tilespec.h"
63 #define WAIT_BETWEEN_TRIES 100000 /* usecs */
64 #define NUMBER_OF_TRIES 500
66 #ifdef WIN32_NATIVE
67 /* FIXME: this is referenced directly in gui-win32/connectdlg.c. */
68 HANDLE server_process = INVALID_HANDLE_VALUE;
69 HANDLE loghandle = INVALID_HANDLE_VALUE;
70 #else
71 static pid_t server_pid = - 1;
72 #endif
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)
88 then:
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
93 we can, then:
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
101 the game.
102 **************************************************************************/
104 /**************************************************************************
105 Tests if the client has started the server.
106 **************************************************************************/
107 bool is_server_running()
109 #ifdef WIN32_NATIVE
110 return (server_process != INVALID_HANDLE_VALUE);
111 #else
112 return (server_pid > 0);
113 #endif
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. */
147 send_chat("/quit");
148 #ifdef WIN32_NATIVE
149 server_process = INVALID_HANDLE_VALUE;
150 loghandle = INVALID_HANDLE_VALUE;
151 #else
152 server_pid = -1;
153 #endif
154 } else if (force) {
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
157 * the server. */
158 #ifdef WIN32_NATIVE
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);
169 server_pid = -1;
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
178 the server.
179 *****************************************************************/
180 bool client_start_server(void)
182 #if !defined(HAVE_WORKING_FORK) && !defined(WIN32_NATIVE)
183 /* Can't do much without fork */
184 return FALSE;
185 #else /* HAVE_WORKING_FORK || WIN32_NATIVE */
186 char buf[512];
187 int connect_tries = 0;
188 # ifdef WIN32_NATIVE
189 STARTUPINFO si;
190 PROCESS_INFORMATION pi;
192 char savesdir[MAX_LEN_PATH];
193 char scensdir[MAX_LEN_PATH];
194 char options[512];
195 char cmdline1[512];
196 char cmdline2[512];
197 char cmdline3[512];
198 char cmdline4[512];
199 char logcmdline[512];
200 char scriptcmdline[512];
201 char savescmdline[512];
202 char scenscmdline[512];
203 # endif /* WIN32_NATIVE */
205 #ifdef IPV6_SUPPORT
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;
212 #else
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..."));
233 return FALSE;
236 # ifdef HAVE_WORKING_FORK
237 server_pid = fork();
239 if (server_pid == 0) {
240 int fd, argc = 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";
249 argv[argc++] = "-p";
250 argv[argc++] = port_buf;
251 argv[argc++] = "--bind";
252 argv[argc++] = "localhost";
253 argv[argc++] = "-q";
254 argv[argc++] = "1";
255 argv[argc++] = "-e";
256 argv[argc++] = "--saves";
257 argv[argc++] = "~/.freeciv/saves";
258 argv[argc++] = "--scenarios";
259 argv[argc++] = "~/.freeciv/scenarios";
260 argv[argc++] = "-A";
261 argv[argc++] = "none";
262 if (logfile) {
263 argv[argc++] = "--debug";
264 argv[argc++] = "3";
265 argv[argc++] = "--log";
266 argv[argc++] = logfile;
268 if (scriptfile) {
269 argv[argc++] = "--read";
270 argv[argc++] = scriptfile;
272 argv[argc] = NULL;
273 fc_assert(argc <= max_nargs);
275 /* avoid terminal spam, but still make server output available */
276 fclose(stdout);
277 fclose(stderr);
279 /* FIXME: include the port to avoid duplication? */
280 if (logfile) {
281 fd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, 0644);
283 if (fd != 1) {
284 dup2(fd, 1);
286 if (fd != 2) {
287 dup2(fd, 2);
289 fchmod(1, 0644);
292 /* If it's still attatched to our terminal, things get messed up,
293 but freeciv-server needs *something* */
294 fclose(stdin);
295 fd = open("/dev/null", O_RDONLY);
296 if (fd != 0) {
297 dup2(fd, 0);
300 /* these won't return on success */
301 #ifdef DEBUG
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);
307 #endif /* DEBUG */
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) */
314 _exit(1);
316 # else /* HAVE_WORKING_FORK */
317 # ifdef WIN32_NATIVE
318 if (logfile) {
319 loghandle = CreateFile(logfile, GENERIC_WRITE,
320 FILE_SHARE_READ | FILE_SHARE_WRITE,
321 NULL,
322 OPEN_ALWAYS, 0, NULL);
325 ZeroMemory(&si, sizeof(si));
326 si.cb = 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. */
333 logcmdline[0] = 0;
334 scriptcmdline[0] = 0;
336 /* the server expects command line arguments to be in local encoding */
337 if (logfile) {
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);
345 if (scriptfile) {
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,
364 scenscmdline);
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);
373 if (
374 #ifdef DEBUG
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)
382 #endif /* DEBUG */
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..."));
392 return FALSE;
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
405 #ifndef WIN32_NATIVE
406 if (waitpid(server_pid, NULL, WNOHANG) != 0) {
407 break;
409 #endif /* WIN32_NATIVE */
410 #endif /* HAVE_WORKING_FORK */
411 if (connect_tries++ > NUMBER_OF_TRIES) {
412 break;
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..."));
427 return FALSE;
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. */
450 char buf[16];
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);
462 return TRUE;
463 #endif /* HAVE_WORKING_FORK || WIN32_NATIVE */
466 /*************************************************************************
467 generate a random string.
468 *************************************************************************/
469 static void randomize_string(char *str, size_t n)
471 const char chars[] =
472 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
473 int i;
475 for (i = 0; i < n - 1; i++) {
476 str[i] = chars[fc_rand(sizeof(chars) - 1)];
478 str[i] = '\0';
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)) {
496 return;
499 /* get the full filename path */
500 interpret_tilde(challenge_fullname, sizeof(challenge_fullname),
501 "~/.freeciv/");
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",
513 challenge_fullname);
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';
535 if (you_have_hack) {
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)
555 if (filename) {
556 send_chat_printf("/save %s", filename);
557 } else {
558 send_chat("/save");
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];
568 int i;
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]);
576 if (len > suf_len
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++) {
584 free(rulesets[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)
594 char buf[4096];
596 fc_snprintf(buf, sizeof(buf), "/read %s%s", ruleset, RULESET_SUFFIX);
597 log_debug("Executing '%s'", buf);
598 send_chat(buf);