Freeciv21
Develop your civilization from humble roots to a global empire
auth.cpp
Go to the documentation of this file.
1 /*
2 _ ._ Copyright (c) 1996-2021 Freeciv21 and Freeciv contributors.
3  \ | This file is part of Freeciv21. Freeciv21 is free software: you
4  \_| can redistribute it and/or modify it under the terms of the
5  .' '. GNU General Public License as published by the Free
6  :O O: Software Foundation, either version 3 of the License,
7  '/ \' or (at your option) any later version. You should have
8  :X: received a copy of the GNU General Public License along with
9  :X: Freeciv21. If not, see https://www.gnu.org/licenses/.
10  */
11 
12 // utility
13 #include "fcintl.h"
14 #include "log.h"
15 #include "shared.h"
16 #include "support.h"
17 
18 // common
19 #include "connection.h"
20 #include "packets.h"
21 
22 /* common/scripting */
23 #include "luascript_types.h"
24 
25 // server
26 #include "connecthand.h"
27 #include "notify.h"
28 #include "srv_main.h"
29 
30 /* server/scripting */
31 #include "script_fcdb.h"
32 
33 #include "auth.h"
34 
35 #define GUEST_NAME "guest"
36 
37 #define MIN_PASSWORD_LEN 6 // minimum length of password
38 #define MAX_AUTH_TRIES 3
39 #define MAX_WAIT_TIME 300 // max time we'll wait on a password
40 
41 /* after each wrong guess for a password, the server waits this
42  * many seconds to reply to the client */
43 static const int auth_fail_wait[] = {1, 1, 2, 3};
44 
45 static bool is_guest_name(const char *name);
46 static void get_unique_guest_name(char *name);
47 static bool is_good_password(const char *password, char *msg);
48 
56 bool auth_user(struct connection *pconn, char *username)
57 {
58  char tmpname[MAX_LEN_NAME] = "\0";
59 
60  /* assign the client a unique guest name/reject if guests aren't allowed */
61  if (is_guest_name(username)) {
63  sz_strlcpy(tmpname, username);
64  get_unique_guest_name(username);
65 
66  if (strncmp(tmpname, username, MAX_LEN_NAME) != 0) {
67  notify_conn_early(pconn->self, nullptr, E_CONNECTION, ftc_warning,
68  _("Warning: the guest name '%s' has been "
69  "taken, renaming to user '%s'."),
70  tmpname, username);
71  }
72  sz_strlcpy(pconn->username, username);
74  } else {
75  reject_new_connection(_("Guests are not allowed on this server. "
76  "Sorry."),
77  pconn);
78  qInfo(_("%s was rejected: Guests not allowed."), username);
79  return false;
80  }
81  } else {
82  /* we are not a guest, we need an extra check as to whether a
83  * connection can be established: the client must authenticate itself */
84  char buffer[MAX_LEN_MSG];
85  bool exists = false;
86 
87  sz_strlcpy(pconn->username, username);
88 
89  if (!script_fcdb_user_exists(pconn, exists)) {
91  sz_strlcpy(tmpname, pconn->username);
92  get_unique_guest_name(tmpname); // don't pass pconn->username here
93  sz_strlcpy(pconn->username, tmpname);
94 
95  qCritical("Error reading database; connection -> guest");
97  pconn->self, nullptr, E_CONNECTION, ftc_warning,
98  _("There was an error reading the user "
99  "database, logging in as guest connection '%s'."),
100  pconn->username);
102  } else {
104  _("There was an error reading the user database "
105  "and guest logins are not allowed. Sorry"),
106  pconn);
107  qInfo(_("%s was rejected: Database error and guests not "
108  "allowed."),
109  pconn->username);
110  return false;
111  }
112  } else if (exists) {
113  // we found a user
114  fc_snprintf(buffer, sizeof(buffer), _("Enter password for %s:"),
115  pconn->username);
116  dsend_packet_authentication_req(pconn, AUTH_LOGIN_FIRST, buffer);
117  pconn->server.auth_settime = time(nullptr);
118  pconn->server.status = AS_REQUESTING_OLD_PASS;
119  } else {
120  // we couldn't find the user, he is new
122  /* TRANS: Try not to make the translation much longer than the
123  * original. */
124  sz_strlcpy(
125  buffer,
126  _("First time login. Set a new password and confirm it."));
127  dsend_packet_authentication_req(pconn, AUTH_NEWUSER_FIRST, buffer);
128  pconn->server.auth_settime = time(nullptr);
129  pconn->server.status = AS_REQUESTING_NEW_PASS;
130  } else {
131  reject_new_connection(_("This server allows only preregistered "
132  "users. Sorry."),
133  pconn);
134  qInfo(_("%s was rejected: Only preregistered users allowed."),
135  pconn->username);
136 
137  return false;
138  }
139  }
140  }
141  return true;
142 }
143 
147 bool auth_handle_reply(struct connection *pconn, char *password)
148 {
149  char msg[MAX_LEN_MSG];
150 
151  if (pconn->server.status == AS_REQUESTING_NEW_PASS) {
152  // check if the new password is acceptable
153  if (!is_good_password(password, msg)) {
154  if (pconn->server.auth_tries++ >= MAX_AUTH_TRIES) {
155  reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
156  qInfo(_("%s was rejected: Too many wrong password "
157  "verifies for new user."),
158  pconn->username);
159 
160  return false;
161  } else {
162  dsend_packet_authentication_req(pconn, AUTH_NEWUSER_RETRY, msg);
163  return true;
164  }
165  }
166 
167  if (!script_fcdb_user_save(pconn, password)) {
168  notify_conn(pconn->self, nullptr, E_CONNECTION, ftc_warning,
169  _("Warning: There was an error in saving to the database. "
170  "Continuing, but your stats will not be saved."));
171  qCritical("Error writing to database for: %s", pconn->username);
172  }
173 
175  } else if (pconn->server.status == AS_REQUESTING_OLD_PASS) {
176  bool success = false;
177 
178  if (script_fcdb_user_verify(pconn, password, success) && success) {
180  } else {
181  pconn->server.status = AS_FAILED;
182  pconn->server.auth_tries++;
183  pconn->server.auth_settime =
184  time(nullptr) + auth_fail_wait[pconn->server.auth_tries];
185  }
186  } else {
187  qDebug("%s is sending unrequested auth packets", pconn->username);
188  return false;
189  }
190 
191  return true;
192 }
193 
197 void auth_process_status(struct connection *pconn)
198 {
199  switch (pconn->server.status) {
200  case AS_NOT_ESTABLISHED:
201  // nothing, we're not ready to do anything here yet.
202  break;
203  case AS_FAILED:
204  /* the connection gave the wrong password, we kick 'em off or
205  * we're throttling the connection to avoid password guessing */
206  if (pconn->server.auth_settime > 0
207  && time(nullptr) >= pconn->server.auth_settime) {
208  if (pconn->server.auth_tries >= MAX_AUTH_TRIES) {
209  pconn->server.status = AS_NOT_ESTABLISHED;
210  reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
211  qInfo(_("%s was rejected: Too many wrong password tries."),
212  pconn->username);
213  connection_close_server(pconn, _("auth failed"));
214  } else {
215  struct packet_authentication_req request;
216 
217  pconn->server.status = AS_REQUESTING_OLD_PASS;
218  request.type = AUTH_LOGIN_RETRY;
219  sz_strlcpy(request.message,
220  _("Your password is incorrect. Try again."));
221  send_packet_authentication_req(pconn, &request);
222  }
223  }
224  break;
227  // waiting on the client to send us a password... don't wait too long
228  if (time(nullptr) >= pconn->server.auth_settime + MAX_WAIT_TIME) {
229  pconn->server.status = AS_NOT_ESTABLISHED;
230  reject_new_connection(_("Sorry, your connection timed out..."), pconn);
231  qInfo(_("%s was rejected: Connection timeout waiting for "
232  "password."),
233  pconn->username);
234  connection_close_server(pconn, _("auth failed"));
235  }
236  break;
237  case AS_ESTABLISHED:
238  // this better fail bigtime
239  fc_assert(pconn->server.status != AS_ESTABLISHED);
240  break;
241  }
242 }
243 
247 static bool is_guest_name(const char *name)
248 {
249  return (fc_strncasecmp(name, GUEST_NAME, qstrlen(GUEST_NAME)) == 0);
250 }
251 
256 static void get_unique_guest_name(char *name)
257 {
258  unsigned int i;
259 
260  // first see if the given name is suitable
261  if (is_guest_name(name) && !conn_by_user(name)) {
262  return;
263  }
264 
265  // next try bare guest name
267  if (!conn_by_user(name)) {
268  return;
269  }
270 
271  // bare name is taken, append numbers
272  for (i = 1;; i++) {
273  fc_snprintf(name, MAX_LEN_NAME, "%s%u", GUEST_NAME, i);
274 
275  // attempt to find this name; if we can't we're good to go
276  if (!conn_by_user(name)) {
277  break;
278  }
279 
280  // Prevent endless loops.
282  }
283 }
284 
291 static bool is_good_password(const char *password, char *msg)
292 {
293  // check password length
294  if (strlen(password) < MIN_PASSWORD_LEN) {
296  _("Your password is too short, the minimum length is %d. "
297  "Try again."),
299  return false;
300  }
301 
302  if (!is_ascii_name(password)) {
304  _("Your password contains illegal characters. Try again."));
305  return false;
306  }
307 
308  // Make sure the message doesn't contain garbage.
309  msg[0] = '\0';
310 
311  return true;
312 }
313 
317 const char *auth_get_username(struct connection *pconn)
318 {
319  fc_assert_ret_val(pconn != nullptr, nullptr);
320  fc_assert_ret_val(conn_is_valid(pconn), nullptr);
321 
322  return pconn->username;
323 }
324 
328 const char *auth_get_ipaddr(struct connection *pconn)
329 {
330  fc_assert_ret_val(pconn != nullptr, nullptr);
331  fc_assert_ret_val(conn_is_valid(pconn), nullptr);
332 
333  return pconn->server.ipaddr;
334 }
static bool is_good_password(const char *password, char *msg)
Verifies that a password is valid.
Definition: auth.cpp:291
#define MIN_PASSWORD_LEN
Definition: auth.cpp:37
const char * auth_get_username(struct connection *pconn)
Get username for connection.
Definition: auth.cpp:317
bool auth_handle_reply(struct connection *pconn, char *password)
Receives a password from a client and verifies it.
Definition: auth.cpp:147
static bool is_guest_name(const char *name)
See if the name qualifies as a guest login name.
Definition: auth.cpp:247
static const int auth_fail_wait[]
Definition: auth.cpp:43
void auth_process_status(struct connection *pconn)
Checks on where in the authentication process we are.
Definition: auth.cpp:197
#define MAX_AUTH_TRIES
Definition: auth.cpp:38
#define GUEST_NAME
Definition: auth.cpp:35
bool auth_user(struct connection *pconn, char *username)
Handle authentication of a user; called by handle_login_request() if authentication is enabled.
Definition: auth.cpp:56
const char * auth_get_ipaddr(struct connection *pconn)
Get connection ip address.
Definition: auth.cpp:328
#define MAX_WAIT_TIME
Definition: auth.cpp:39
static void get_unique_guest_name(char *name)
Return a unique guest name WARNING: do not pass pconn->username to this function: it won't return!
Definition: auth.cpp:256
void reject_new_connection(const char *msg, struct connection *pconn)
send the rejection packet to the client.
void connection_close_server(struct connection *pconn, const QString &reason)
Close a connection.
void establish_new_connection(struct connection *pconn)
This is used when a new player joins a server, before the game has started.
struct connection * conn_by_user(const char *user_name)
Find connection by exact user name, from game.all_connections, case-insensitve.
Definition: connection.cpp:329
bool conn_is_valid(const struct connection *pconn)
Returns TRUE if the connection is valid, i.e.
Definition: connection.cpp:864
@ AS_REQUESTING_OLD_PASS
Definition: connection.h:87
@ AS_FAILED
Definition: connection.h:85
@ AS_NOT_ESTABLISHED
Definition: connection.h:84
@ AS_REQUESTING_NEW_PASS
Definition: connection.h:86
@ AS_ESTABLISHED
Definition: connection.h:88
#define MAX_NUM_PLAYERS
Definition: fc_types.h:28
#define MAX_LEN_NAME
Definition: fc_types.h:61
#define _(String)
Definition: fcintl.h:50
const struct ft_color ftc_warning
const char * name
Definition: inputfile.cpp:118
#define fc_assert_ret(condition)
Definition: log.h:112
#define fc_assert(condition)
Definition: log.h:89
#define fc_assert_ret_val(condition, val)
Definition: log.h:114
void notify_conn(struct conn_list *dest, const struct tile *ptile, enum event_type event, const struct ft_color color, const char *format,...)
See notify_conn_packet - this is just the "non-v" version, with varargs.
Definition: notify.cpp:235
void notify_conn_early(struct conn_list *dest, const struct tile *ptile, enum event_type event, const struct ft_color color, const char *format,...)
See notify_conn_packet - this is just the "non-v" version, with varargs.
Definition: notify.cpp:258
#define MAX_LEN_MSG
Definition: packets.h:37
@ AUTH_NEWUSER_RETRY
Definition: packets.h:71
@ AUTH_NEWUSER_FIRST
Definition: packets.h:69
@ AUTH_LOGIN_RETRY
Definition: packets.h:70
@ AUTH_LOGIN_FIRST
Definition: packets.h:68
bool script_fcdb_user_save(connection *pconn, const char *password)
Save a new user.
bool script_fcdb_user_exists(connection *pconn, bool &exists)
Check if the user exists.
bool script_fcdb_user_verify(connection *pconn, const char *username, bool &success)
Check the credentials of the user.
bool is_ascii_name(const char *name)
This is used in sundry places to make sure that names of cities, players etc.
Definition: shared.cpp:223
struct server_arguments srvarg
Definition: srv_main.cpp:118
struct conn_list * self
Definition: connection.h:150
struct connection::@55::@61 server
char username[MAX_LEN_NAME]
Definition: connection.h:151
bool auth_allow_guests
Definition: srv_main.h:62
bool auth_allow_newusers
Definition: srv_main.h:63
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
size_t fc_strlcpy(char *dest, const char *src, size_t n)
fc_strlcpy() provides utf-8 version of (non-standard) function strlcpy() It is intended as more user-...
Definition: support.cpp:412
int fc_strncasecmp(const char *str0, const char *str1, size_t n)
Compare strings like strncmp(), but ignoring case.
Definition: support.cpp:100
#define sz_strlcpy(dest, src)
Definition: support.h:140