Freeciv21
Develop your civilization from humble roots to a global empire
connectdlg_common.cpp
Go to the documentation of this file.
1 /*
2  Copyright (c) 1996-2023 Freeciv21 and Freeciv contributors. This file is
3  __ __ part of Freeciv21. Freeciv21 is free software: you can
4 / \\..// \ redistribute it and/or modify it under the terms of the GNU
5  ( oo ) General Public License as published by the Free Software
6  \__/ Foundation, either version 3 of the License, or (at your
7  option) any later version. You should have received
8  a copy of the GNU General Public License along with Freeciv21. If not,
9  see https://www.gnu.org/licenses/.
10  */
11 #include <fc_config.h>
12 
13 // Qt
14 #include <QCoreApplication>
15 #include <QDebug>
16 #include <QDir>
17 #include <QProcess>
18 #include <QStandardPaths>
19 #include <QTcpServer>
20 #include <QUrl>
21 #include <QUuid>
22 
23 #include <cstring>
24 
25 #ifdef FREECIV_MSWINDOWS
26 #include <windows.h>
27 #endif
28 
29 // utility
30 #include "fcintl.h"
31 #include "log.h"
32 #include "rand.h"
33 #include "registry.h"
34 #include "registry_ini.h"
35 #include "shared.h"
36 #include "support.h"
37 
38 // client
39 #include "chatline_common.h"
40 #include "client_main.h"
41 #include "clinet.h" // connect_to_server()
42 #include "connectdlg_common.h"
43 #include "packhand_gen.h"
44 #include "qtg_cxxside.h"
45 
46 #define WAIT_BETWEEN_TRIES 100000 // usecs
47 #define NUMBER_OF_TRIES 500
48 
49 bool server_quitting = false;
50 
52 static bool client_has_hack = false;
53 
55 
56 class serverProcess : public QProcess {
58 
59 public:
60  static serverProcess *i();
61 
62 private:
63  void drop();
64  serverProcess();
66 };
68 
69 // Server process constructor
71 {
72  connect(this,
73  QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
74  [=](int exitCode, QProcess::ExitStatus exitStatus) {
75  Q_UNUSED(exitCode)
76  qInfo() << _("Freeciv21 Server") << exitStatus;
77  drop();
78  });
79 }
80 // Server process instance
82 {
83  if (!m_instance) {
85  }
86  return m_instance;
87 }
88 // Removes server process
90 {
91  if (m_instance) {
92  m_instance->deleteLater();
93  m_instance = 0;
94  }
95 }
96 
126 {
127  if (server_quitting) {
128  return false;
129  }
130  return serverProcess::i()->state() > QProcess::NotRunning;
131 }
132 
137 
145 void client_kill_server(bool force)
146 {
147  if (is_server_running()) {
148  if (client.conn.used && client_has_hack) {
149  /* This does a "soft" shutdown of the server by sending a /quit.
150  *
151  * This is useful when closing the client or disconnecting because it
152  * doesn't kill the server prematurely. In particular, killing the
153  * server in the middle of a save can have disastrous results. This
154  * method tells the server to quit on its own. This is safer from a
155  * game perspective, but more dangerous because if the kill fails the
156  * server will be left running.
157  *
158  * Another potential problem is because this function is called atexit
159  * it could potentially be called when we're connected to an unowned
160  * server. In this case we don't want to kill it. */
161  send_chat("/quit");
162  server_quitting = true;
163  } else if (force) {
164  /* Either we already disconnected, or we didn't get control of the
165  * server. In either case, the only thing to do is a "hard" kill of
166  * the server. */
167  serverProcess::i()->kill();
168  server_quitting = false;
169  }
170  }
171 
172  client_has_hack = false;
173 }
174 
179 bool client_start_server(const QString &user_name)
180 {
181  QStringList arguments;
182  QString trueFcser, storage, port_buf, savesdir, scensdir;
183  char buf[512];
184  int connect_tries = 0;
185 
186  // only one server (forked from this client) shall be running at a time
187  // This also resets client_has_hack.
188  client_kill_server(true);
189 
190  output_window_append(ftc_client, _("Starting local server..."));
191 
192  storage = freeciv_storage_dir();
193  if (storage == nullptr) {
195  _("Cannot find Freeciv21 storage directory"));
197  ftc_client, _("You'll have to start server manually. Sorry..."));
198  return false;
199  }
200 
201  // Generate a (random) unique name for the local socket
202  auto uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
203 
204  // Set up the command-line parameters.
205  savesdir = QStringLiteral("%1/saves").arg(storage);
206  scensdir = QStringLiteral("%1/scenarios").arg(storage);
207 
208  arguments << QStringLiteral("--local") << uuid << QStringLiteral("-q")
209  << QStringLiteral("1") << QStringLiteral("-e")
210  << QStringLiteral("--saves") << savesdir
211  << QStringLiteral("--scenarios") << scensdir
212  << QStringLiteral("-A") << QStringLiteral("none");
213  if (!logfile.isEmpty()) {
214  arguments << QStringLiteral("--debug") << log_get_level()
215  << QStringLiteral("--log") << logfile;
216  }
217  if (scriptfile.isEmpty()) {
218  arguments << QStringLiteral("--read") << scriptfile;
219  }
220  if (savefile.isEmpty()) {
221  arguments << QStringLiteral("--file") << savefile;
222  }
223 
224  // Look for a server binary
225  const QString server_name = QStringLiteral("freeciv21-server");
226 
227  // First next to the client binary
228  // NOTE On Windows findExecutable adds the .exe automatically
229  QString location = QStandardPaths::findExecutable(
230  server_name, {QCoreApplication::applicationDirPath()});
231  if (location.isEmpty()) {
232  // Then in PATH
233  location = QStandardPaths::findExecutable(server_name);
234  }
235 
236  // Start it
237  qInfo(_("Starting freeciv21-server at %s"), qUtf8Printable(location));
238 
239  serverProcess::i()->start(location, arguments);
240  if (!serverProcess::i()->waitForStarted(3000)) {
241  output_window_append(ftc_client, _("Couldn't start the server."));
243  _("We probably couldn't start it from here."));
245  _("You'll have to start one manually. Sorry..."));
246  return false;
247  }
248 
249  // Wait for the server to print its welcome screen
250  serverProcess::i()->waitForReadyRead();
251  serverProcess::i()->waitForStarted();
252  server_quitting = false;
253 
254  // Local server URL
255  auto url = QUrl();
256  url.setScheme(QStringLiteral("fc21+local"));
257  url.setUserName(user_name);
258  url.setPath(uuid);
259 
260  // a reasonable number of tries
261  while (connect_to_server(
262  url, buf,
263  sizeof(buf) && serverProcess::i()->state() == QProcess::Running)
264  == -1) {
266  if (connect_tries++ > NUMBER_OF_TRIES) {
267  qCritical("Last error from connect attempts: '%s'", buf);
268  break;
269  }
270  }
271  /* weird, but could happen, if server doesn't support new startup stuff
272  * capabilities won't help us here... */
273  if (!client.conn.used || serverProcess::i()->processId() == 0) {
274  // possible that server is still running. kill it, kill it with Igni
275  client_kill_server(true);
276 
277  qCritical("Failed to connect to spawned server!");
278 #ifdef FREECIV_DEBUG
279  qDebug("Tried with commandline: '%s'",
280  QString(trueFcser + arguments.join(QStringLiteral(" ")))
281  .toLocal8Bit()
282  .data());
283 #endif
284  output_window_append(ftc_client, _("Couldn't connect to the server."));
286  _("We probably couldn't start it from here."));
288  _("You'll have to start one manually. Sorry..."));
289 
290  return false;
291  }
292 
293  return true;
294 }
295 
299 static void randomize_string(char *str, size_t n)
300 {
301  const char chars[] =
302  "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
303  int i;
304 
305  for (i = 0; i < n - 1; i++) {
306  str[i] = chars[fc_rand(sizeof(chars) - 1)];
307  }
308  str[i] = '\0';
309 }
310 
319 void send_client_wants_hack(const char *filename)
320 {
321  if (filename[0] != '\0') {
322  struct packet_single_want_hack_req req;
323  struct section_file *file;
324  auto sdir = freeciv_storage_dir();
325 
326  if (sdir.isEmpty()) {
327  return;
328  }
329 
330  if (!is_safe_filename(filename)) {
331  return;
332  }
333 
334  QDir().mkpath(sdir);
335 
337  qUtf8Printable(sdir), filename);
338 
339  // generate an authentication token
340  randomize_string(req.token, sizeof(req.token));
341 
342  file = secfile_new(false);
343  secfile_insert_str(file, req.token, "challenge.token");
344  if (!secfile_save(file, challenge_fullname)) {
345  qCritical("Couldn't write token to temporary file: %s",
347  }
348  secfile_destroy(file);
349 
350  // tell the server what we put into the file
351  send_packet_single_want_hack_req(&client.conn, &req);
352  }
353 }
354 
358 void handle_single_want_hack_reply(bool you_have_hack)
359 {
360  // remove challenge file
361  if (challenge_fullname[0] != '\0') {
362  if (fc_remove(challenge_fullname) == -1) {
363  qCritical("Couldn't remove temporary file: %s", challenge_fullname);
364  }
365  challenge_fullname[0] = '\0';
366  }
367 
368  if (you_have_hack) {
370  _("Established control over the server. "
371  "You have command access level 'hack'."));
372  client_has_hack = true;
373  } else if (is_server_running()) {
374  // only output this if we started the server and we NEED hack
376  _("Failed to obtain the required access "
377  "level to take control of the server. "
378  "Attempting to shut down server."));
379  client_kill_server(true);
380  }
381 }
382 
386 void send_save_game(const char *filename)
387 {
388  if (filename) {
389  send_chat_printf("/save %s", filename);
390  } else {
391  send_chat("/save");
392  }
393 }
394 
398 void handle_ruleset_choices(const struct packet_ruleset_choices *packet)
399 {
400  QStringList rulesets;
401  int i;
402 
403  for (i = 0; i < packet->ruleset_count; i++) {
404  QString r = packet->rulesets[i];
405 
406  int id = r.lastIndexOf(RULESET_SUFFIX);
407  if (id >= 0) {
408  r = r.left(id);
409  }
410  rulesets.append(r);
411  }
412  set_rulesets(packet->ruleset_count, rulesets);
413 }
414 
419 void set_ruleset(const char *ruleset)
420 {
421  char buf[4096];
422 
423  fc_snprintf(buf, sizeof(buf), "/read %s%s", ruleset, RULESET_SUFFIX);
424  log_debug("Executing '%s'", buf);
425  send_chat(buf);
426 }
int send_chat_printf(const char *format,...)
Send the message as a chat to the server.
int send_chat(const char *message)
Send the message as a chat to the server.
void output_window_append(const struct ft_color color, const char *featured_text)
Add a line of text to the output ("chatline") window, like puts() would do it in the console.
static QString ruleset
Definition: civmanual.cpp:152
Q_DISABLE_COPY(serverProcess)
static serverProcess * i()
static serverProcess * m_instance
QString savefile
QString logfile
struct civclient client
QString scriptfile
int connect_to_server(const QUrl &url, char *errbuf, int errbufsize)
Connect to a freeciv21-server instance – or at least try to.
Definition: clinet.cpp:167
bool can_client_access_hack()
Returns TRUE if the client has hack access.
#define NUMBER_OF_TRIES
void handle_ruleset_choices(const struct packet_ruleset_choices *packet)
Handle the list of rulesets sent by the server.
bool server_quitting
void handle_single_want_hack_reply(bool you_have_hack)
Handle response (by the server) if the client has got hack or not.
void send_client_wants_hack(const char *filename)
If the client is capable of 'wanting hack', then the server will send the client a filename in the pa...
bool is_server_running()
The general chain of events:
static bool client_has_hack
int internal_server_port
void send_save_game(const char *filename)
Send server command to save game.
static void randomize_string(char *str, size_t n)
Generate a random string.
#define WAIT_BETWEEN_TRIES
void set_ruleset(const char *ruleset)
Called by the GUI code when the user sets the ruleset.
bool client_start_server(const QString &user_name)
Forks a server if it can.
void client_kill_server(bool force)
Kills the server if the client has started it.
static char challenge_fullname[MAX_LEN_PATH]
void set_rulesets(int num_rulesets, QStringList rulesets)
Set the list of available rulesets.
Definition: fc_client.cpp:580
#define RULESET_SUFFIX
Definition: fc_types.h:329
#define _(String)
Definition: fcintl.h:50
const struct ft_color ftc_client
const QString & log_get_level()
Retrieves the log level passed to log_init (even if log_init failed).
Definition: log.cpp:207
#define log_debug(message,...)
Definition: log.h:65
#define fc_rand(_size)
Definition: rand.h:16
struct section_file * secfile_new(bool allow_duplicates)
Create a new empty section file.
void secfile_destroy(struct section_file *secfile)
Free a section file.
bool secfile_save(const struct section_file *secfile, QString filename)
Save the previously filled in section_file to disk.
#define secfile_insert_str(secfile, string, path,...)
Definition: registry_ini.h:167
bool is_safe_filename(const QString &name)
Check if the name is safe security-wise.
Definition: shared.cpp:210
QString freeciv_storage_dir()
Returns string which gives freeciv storage dir.
Definition: shared.cpp:419
#define MAX_LEN_PATH
Definition: shared.h:28
struct connection conn
Definition: client_main.h:89
int fc_snprintf(char *str, size_t n, const char *format,...)
See also fc_utf8_snprintf_trunc(), fc_utf8_snprintf_rep().
Definition: support.cpp:537
void fc_usleep(unsigned long usec)
Suspend execution for the specified number of microseconds.
Definition: support.cpp:349
int fc_remove(const char *filename)
Wrapper function for remove() with filename conversion to local encoding on Windows.
Definition: support.cpp:274