File: auth.c

package info (click to toggle)
freeciv 3.2.2%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 286,492 kB
  • sloc: ansic: 484,452; cpp: 37,766; sh: 10,374; makefile: 7,425; python: 2,938; xml: 652; sed: 11
file content (363 lines) | stat: -rw-r--r-- 12,657 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/***********************************************************************
 Freeciv - Copyright (C) 2005  - M.C. Kaufman
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
***********************************************************************/

#ifdef HAVE_CONFIG_H
#include <fc_config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* utility */
#include "fcintl.h"
#include "log.h"
#include "md5.h"
#include "registry.h"
#include "shared.h"
#include "support.h"

/* common */
#include "connection.h"
#include "packets.h"

/* common/scripting */
#include "luascript_types.h"

/* server */
#include "connecthand.h"
#include "fcdb.h"
#include "notify.h"
#include "sernet.h"
#include "srv_main.h"

/* server/scripting */
#include "script_fcdb.h"

#include "auth.h"

#define GUEST_NAME "guest"

#define MIN_PASSWORD_LEN  6  /* minimum length of password */
#define MIN_PASSWORD_CAPS 0  /* minimum number of capital letters required */
#define MIN_PASSWORD_NUMS 0  /* minimum number of numbers required */

#define MAX_AUTH_TRIES 3
#define MAX_WAIT_TIME 300   /* max time we'll wait on a password */

/* after each wrong guess for a password, the server waits this
 * many seconds to reply to the client */
static const int auth_fail_wait[] = { 1, 1, 2, 3 };

static bool is_guest_name(const char *name);
static void get_unique_guest_name(char *name);
static bool is_good_password(const char *password, char *msg);

/************************************************************************//**
  Handle authentication of a user; called by handle_login_request() if
  authentication is enabled.

  If the connection is rejected right away, return FALSE, otherwise this
  function will return TRUE.
****************************************************************************/
bool auth_user(struct connection *pconn, char *username)
{
  char tmpname[MAX_LEN_NAME] = "\0";

  /* Assign the client a unique guest name/Reject if guests aren't allowed */
  if (is_guest_name(username)) {
    if (srvarg.auth_allow_guests) {

      sz_strlcpy(tmpname, username);
      get_unique_guest_name(username);

      if (fc_strncmp(tmpname, username, MAX_LEN_NAME)) {
        notify_conn_early(pconn->self, NULL, E_CONNECTION, ftc_warning,
                          _("Warning: the guest name '%s' has been "
                            "taken, renaming to user '%s'."), tmpname, username);
      }
      sz_strlcpy(pconn->username, username);
      establish_new_connection(pconn);
    } else {
      reject_new_connection(_("Guests are not allowed on this server. "
                              "Sorry."), pconn);
      log_normal(_("%s was rejected: Guests not allowed."), username);
      return FALSE;
    }
  } else {
    /* We are not a guest, we need an extra check as to whether a
     * connection can be established: the client must authenticate itself */
    char buffer[MAX_LEN_MSG];
    bool exists = FALSE;

    sz_strlcpy(pconn->username, username);

    if (!script_fcdb_call("user_exists", pconn, &exists)) {
      if (srvarg.auth_allow_guests) {
        sz_strlcpy(tmpname, pconn->username);
        get_unique_guest_name(tmpname); /* don't pass pconn->username here */
        sz_strlcpy(pconn->username, tmpname);

        log_error("Error reading database; connection -> guest");
        notify_conn_early(pconn->self, NULL, E_CONNECTION, ftc_warning,
                          _("There was an error reading the user "
                            "database, logging in as guest connection '%s'."),
                          pconn->username);
        establish_new_connection(pconn);
      } else {
        reject_new_connection(_("There was an error reading the user database "
                                "and guest logins are not allowed. Sorry"),
                              pconn);
        log_normal(_("%s was rejected: Database error and guests not "
                     "allowed."), pconn->username);
        return FALSE;
      }
    } else if (exists) {
      /* we found a user */
      fc_snprintf(buffer, sizeof(buffer), _("Enter password for %s:"),
                  pconn->username);
      dsend_packet_authentication_req(pconn, AUTH_LOGIN_FIRST, buffer);
      pconn->server.auth_settime = time(NULL);
      pconn->server.status = AS_REQUESTING_OLD_PASS;
    } else {
      /* we couldn't find the user, they are new */
      if (srvarg.auth_allow_newusers) {
        /* TRANS: Try not to make the translation much longer than the original. */
        sz_strlcpy(buffer, _("First time login. Set a new password and confirm it."));
        dsend_packet_authentication_req(pconn, AUTH_NEWUSER_FIRST, buffer);
        pconn->server.auth_settime = time(NULL);
        pconn->server.status = AS_REQUESTING_NEW_PASS;
      } else {
        reject_new_connection(_("This server allows only preregistered "
                                "users. Sorry."), pconn);
        log_normal(_("%s was rejected: Only preregistered users allowed."),
                   pconn->username);

        return FALSE;
      }
    }
  }
  return TRUE;
}

/************************************************************************//**
  Receives a password from a client and verifies it.
****************************************************************************/
bool auth_handle_reply(struct connection *pconn, char *password)
{
  char msg[MAX_LEN_MSG];

  if (pconn->server.status == AS_REQUESTING_NEW_PASS) {

    /* check if the new password is acceptable */
    if (!is_good_password(password, msg)) {
      if (pconn->server.auth_tries++ >= MAX_AUTH_TRIES) {
        reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
        log_normal(_("%s was rejected: Too many wrong password "
                     "verifies for new user."), pconn->username);

        return FALSE;
      } else {
        dsend_packet_authentication_req(pconn, AUTH_NEWUSER_RETRY, msg);
        return TRUE;
      }
    }

    if (!script_fcdb_call("user_save", pconn, password)) {
      notify_conn(pconn->self, NULL, E_CONNECTION, ftc_warning,
                  _("Warning: There was an error in saving to the database. "
                    "Continuing, but your stats will not be saved."));
      log_error("Error writing to database for: %s", pconn->username);
    }

    establish_new_connection(pconn);
  } else if (pconn->server.status == AS_REQUESTING_OLD_PASS) {
    bool success = FALSE;

    if (script_fcdb_call("user_verify", pconn, password, &success)
        && success) {
      establish_new_connection(pconn);
    } else {
      pconn->server.status = AS_FAILED;
      pconn->server.auth_tries++;
      pconn->server.auth_settime = time(NULL)
                                   + auth_fail_wait[pconn->server.auth_tries];
    }
  } else {
    log_verbose("%s is sending unrequested auth packets", pconn->username);
    return FALSE;
  }

  return TRUE;
}

/************************************************************************//**
  Checks on where in the authentication process we are.
****************************************************************************/
void auth_process_status(struct connection *pconn)
{
  switch (pconn->server.status) {
  case AS_NOT_ESTABLISHED:
    /* nothing, we're not ready to do anything here yet. */
    break;
  case AS_FAILED:
    /* the connection gave the wrong password, we kick 'em off or
     * we're throttling the connection to avoid password guessing */
    if (pconn->server.auth_settime > 0
        && time(NULL) >= pconn->server.auth_settime) {

      if (pconn->server.auth_tries >= MAX_AUTH_TRIES) {
        pconn->server.status = AS_NOT_ESTABLISHED;
        reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
        log_normal(_("%s was rejected: Too many wrong password tries."),
                   pconn->username);
        connection_close_server(pconn, _("auth failed"));
      } else {
        struct packet_authentication_req request;

        pconn->server.status = AS_REQUESTING_OLD_PASS;
        request.type = AUTH_LOGIN_RETRY;
        sz_strlcpy(request.message,
                   _("Your password is incorrect. Try again."));
        send_packet_authentication_req(pconn, &request);
      }
    }
    break;
  case AS_REQUESTING_OLD_PASS:
  case AS_REQUESTING_NEW_PASS:
    /* waiting on the client to send us a password... don't wait too long */
    if (time(NULL) >= pconn->server.auth_settime + MAX_WAIT_TIME) {
      pconn->server.status = AS_NOT_ESTABLISHED;
      reject_new_connection(_("Sorry, your connection timed out..."), pconn);
      log_normal(_("%s was rejected: Connection timeout waiting for "
                   "password."), pconn->username);
      connection_close_server(pconn, _("auth failed"));
    }
    break;
  case AS_ESTABLISHED:
    /* this better fail bigtime */
    fc_assert(pconn->server.status != AS_ESTABLISHED);
    break;
  }
}

/************************************************************************//**
  See if the name qualifies as a guest login name
****************************************************************************/
static bool is_guest_name(const char *name)
{
  return (fc_strncasecmp(name, GUEST_NAME, strlen(GUEST_NAME)) == 0);
}

/************************************************************************//**
  Return a unique guest name
  WARNING: do not pass pconn->username to this function: it won't return!
****************************************************************************/
static void get_unique_guest_name(char *name)
{
  unsigned int i;

  /* first see if the given name is suitable */
  if (is_guest_name(name) && !conn_by_user(name)) {
    return;
  }

  /* next try bare guest name */
  fc_strlcpy(name, GUEST_NAME, MAX_LEN_NAME);
  if (!conn_by_user(name)) {
    return;
  }

  /* bare name is taken, append numbers */
  for (i = 1; ; i++) {
    fc_snprintf(name, MAX_LEN_NAME, "%s%u", GUEST_NAME, i);

    /* attempt to find this name; if we can't we're good to go */
    if (!conn_by_user(name)) {
      break;
    }

    /* Prevent endless loops. */
    fc_assert_ret(i < 2 * MAX_NUM_PLAYERS);
  }
}

/************************************************************************//**
  Verifies that a password is valid. Does some [very] rudimentary safety
  checks. TODO: do we want to frown on non-printing characters?
  Fill the msg (length MAX_LEN_MSG) with any worthwhile information that
  the client ought to know.
****************************************************************************/
static bool is_good_password(const char *password, char *msg)
{
  int i, num_caps = 0, num_nums = 0;

  /* check password length */
  if (strlen(password) < MIN_PASSWORD_LEN) {
    fc_snprintf(msg, MAX_LEN_MSG,
                _("Your password is too short, the minimum length is %d. "
                  "Try again."), MIN_PASSWORD_LEN);
    return FALSE;
  }

  fc_snprintf(msg, MAX_LEN_MSG,
              _("The password must have at least %d capital letters, %d "
                "numbers, and be at minimum %d [printable] characters long. "
                "Try again."), 
              MIN_PASSWORD_CAPS, MIN_PASSWORD_NUMS, MIN_PASSWORD_LEN);

  for (i = 0; i < strlen(password); i++) {
    if (fc_isupper(password[i])) {
      num_caps++;
    }
    if (fc_isdigit(password[i])) {
      num_nums++;
    }
  }

  /* check number of capital letters */
  if (num_caps < MIN_PASSWORD_CAPS) {
    return FALSE;
  }

  /* check number of numbers */
  if (num_nums < MIN_PASSWORD_NUMS) {
    return FALSE;
  }

  if (!is_ascii_name(password)) {
    return FALSE;
  }

  return TRUE;
}

/************************************************************************//**
  Get username for connection
****************************************************************************/
const char *auth_get_username(struct connection *pconn)
{
  fc_assert_ret_val(pconn != NULL, NULL);

  return pconn->username;
}

/************************************************************************//**
  Get connection ip address
****************************************************************************/
const char *auth_get_ipaddr(struct connection *pconn)
{
  fc_assert_ret_val(pconn != NULL, NULL);

  return pconn->server.ipaddr;
}