Complete.Org: Mailing Lists: Archives: freeciv-dev: December 2005:
[Freeciv-Dev] (PR#14851) remove userdb, add mysql to server for authenti
Home

[Freeciv-Dev] (PR#14851) remove userdb, add mysql to server for authenti

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Subject: [Freeciv-Dev] (PR#14851) remove userdb, add mysql to server for authentication
From: "Mike Kaufman" <kaufman@xxxxxxxxxxxxxxxxxxxxxx>
Date: Mon, 12 Dec 2005 21:29:36 -0800
Reply-to: bugs@xxxxxxxxxxx

<URL: http://bugs.freeciv.org/Ticket/Display.html?id=14851 >

here's patches for both 2.0 and trunk to remove userdb/ and add mysql
authentication to the server.

To apply, pick your version. 

If 2.0:
 install mysql (table creation instruction can be found in auth.c)
 patch -p0 < auth_mysql-2.0.diff
 patch < auth.c2auth-2.0.c.diff
 add auth.[ch] to server/
 add md5.[ch] to utility/ (use the files in trunk)
 ./autogen.sh --enable-auth

if trunk:
 install mysql
 patch -p0 < auth_mysql.diff
 add auth.[ch] to server/ (use the same auth.h)
 add md5.[ch] to utility/
 ./autogen.sh --enable-auth

-mike

Index: m4/auth.m4
===================================================================
--- m4/auth.m4  (revision 11340)
+++ m4/auth.m4  (working copy)
@@ -4,16 +4,33 @@
 
 AC_DEFUN([FC_CHECK_AUTH],
 [
-  # If the user calls --without-auth this will fail.
-  AC_ARG_WITH([auth],
-             [  --with-auth=lib         Specify authentication database 
library],
-             [USER_DB_LIB="$withval"], [USER_DB_LIB="yes"])
+  dnl  no=do not compile in authentication,  yes=compile in auth,  *=error
+  AC_ARG_ENABLE(auth, 
+  [  --enable-auth        do not compile in authentication],
+  [case "${enableval}" in
+    yes) auth=true ;;
+    no)  auth=false ;;
+    *)   AC_MSG_ERROR(bad value ${enableval} for --enable-auth) ;;
+   esac], [auth=false])
 
-  if test "$USER_DB_LIB" = "yes" || test "$USER_DB_LIB" = "no"; then
-    USER_DB_LIB="userdb/libuserdb.a"
+  if test x$auth = xtrue; then
+
+    AC_CHECK_HEADER(mysql/mysql.h, , 
+                    [AC_MSG_WARN([couldn't find mysql header: disabling auth]);
+                     auth=false])
+
+    AC_CHECK_LIB(mysqlclient, mysql_query, 
+                [AUTH_LIBS="-lmysqlclient $AUTH_LIBS";
+                  AUTH_DEPS="$AUTH_LIBS"],
+                 [AC_MSG_WARN([couldn't find mysql libs: disabling auth]);
+                  auth=false])
+
+    AC_SUBST(AUTH_LIBS)
+    AC_SUBST(AUTH_DEPS)
   fi
-  USER_DB_DEP="$USER_DB_LIB"
 
-  AC_SUBST(USER_DB_LIB)
-  AC_SUBST(USER_DB_DEP)
+  if test x$auth = xtrue; then
+    AC_DEFINE(HAVE_AUTH, 1, [can compile with authentication])
+  fi
+
 ])
Index: configure.ac
===================================================================
--- configure.ac        (revision 11340)
+++ configure.ac        (working copy)
@@ -704,7 +704,6 @@
          client/gui-stub/Makefile 
          server/Makefile 
          server/generator/Makefile
-         server/userdb/Makefile
          server/scripting/Makefile
          manual/Makefile
          intl/Makefile
Index: server/civserver.c
===================================================================
--- server/civserver.c  (revision 11340)
+++ server/civserver.c  (working copy)
@@ -163,12 +163,14 @@
        break;
       }
       free(option);
+#ifdef HAVE_AUTH
     } else if (is_option("--auth", argv[inx])) {
       srvarg.auth_enabled = TRUE;
     } else if (is_option("--Guests", argv[inx])) {
       srvarg.auth_allow_guests = TRUE;
     } else if (is_option("--Newusers", argv[inx])) {
       srvarg.auth_allow_newusers = TRUE;
+#endif
     } else if ((option = get_option_malloc("--Serverid", argv, &inx, argc))) {
       sz_strlcpy(srvarg.serverid, option);
       free(option);
@@ -195,11 +197,13 @@
   if (showhelp) {
     fc_fprintf(stderr,
               _("Usage: %s [option ...]\nValid options are:\n"), argv[0]);
+#ifdef HAVE_AUTH
     fc_fprintf(stderr, _("  -a  --auth\t\tEnable server authentication.\n"));
     fc_fprintf(stderr, _("  -G  --Guests\t\tAllow guests to "
                         "login if auth is enabled.\n"));
     fc_fprintf(stderr, _("  -N  --Newusers\tAllow new users to "
                         "login if auth is enabled.\n"));
+#endif
     fc_fprintf(stderr, _("  -b  --bind ADDR\tListen for clients on ADDR\n"));
 #ifdef DEBUG
     fc_fprintf(stderr, _("  -d, --debug NUM\tSet debug log level (0 to 4,"
Index: server/srv_main.c
===================================================================
--- server/srv_main.c   (revision 11340)
+++ server/srv_main.c   (working copy)
@@ -69,6 +69,7 @@
 #include "timing.h"
 #include "version.h"
 
+#include "auth.h"
 #include "barbarian.h"
 #include "cityhand.h"
 #include "citytools.h"
Index: server/connecthand.c
===================================================================
--- server/connecthand.c        (revision 11340)
+++ server/connecthand.c        (working copy)
@@ -29,7 +29,7 @@
 #include "support.h"
 #include "version.h"
 
-#include "user_db.h"
+#include "auth.h"
 #include "diplhand.h"
 #include "gamehand.h"
 #include "gamelog.h"
@@ -43,25 +43,6 @@
 
 #include "connecthand.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_AUTHENTICATION_TRIES 3
-
-/* after each wrong guess for a password, the server waits this
- * many seconds to reply to the client */
-static const int auth_fail_period[] = { 1, 1, 2, 3 };
-
-static void establish_new_connection(struct connection *pconn);
-static void reject_new_connection(const char *msg, struct connection *pconn);
-
-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);
-
 /**************************************************************************
   This is used when a new player joins a server, before the game
   has started.  If pconn is NULL, is an AI, else a client.
@@ -69,7 +50,7 @@
   N.B. this only attachs a connection to a player if 
        pconn->name == player->username
 **************************************************************************/
-static void establish_new_connection(struct connection *pconn)
+void establish_new_connection(struct connection *pconn)
 {
   struct conn_list *dest = pconn->self;
   struct player *pplayer;
@@ -203,7 +184,7 @@
 /**************************************************************************
   send the rejection packet to the client.
 **************************************************************************/
-static void reject_new_connection(const char *msg, struct connection *pconn)
+void reject_new_connection(const char *msg, struct connection *pconn)
 {
   struct packet_server_join_reply packet;
 
@@ -295,256 +276,15 @@
   } conn_list_iterate_end;
 
   if (srvarg.auth_enabled) {
-    /* assign the client a unique guest name/reject if guests aren't allowed */
-    if (is_guest_name(req->username)) {
-      if (srvarg.auth_allow_guests) {
-        char old_guest_name[MAX_LEN_NAME];
-
-        sz_strlcpy(old_guest_name, req->username);
-        get_unique_guest_name(req->username);
-
-        if (strncmp(old_guest_name, req->username, MAX_LEN_NAME) != 0) {
-          notify_conn(pconn->self, NULL, E_CONNECTION,
-                     _("Warning: the guest name '%s' has been "
-                       "taken, renaming to user '%s'."),
-                      old_guest_name, req->username);
-        }
-      } else {
-        reject_new_connection(_("Guests are not allowed on this server. "
-                                "Sorry."), pconn);
-        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 tmpname[MAX_LEN_NAME] = "\0";
-      char buffer[MAX_LEN_MSG];
-
-      sz_strlcpy(pconn->username, req->username);
-
-      switch(user_db_load(pconn)) {
-      case USER_DB_ERROR:
-        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);
-
-          freelog(LOG_ERROR, "Error reading database; connection -> guest");
-          notify_conn(pconn->self, NULL, E_CONNECTION,
-                     _("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);
-          return FALSE;
-        }
-        break;
-      case USER_DB_SUCCESS:
-        /* we found a user */
-        my_snprintf(buffer, sizeof(buffer), _("Enter password for %s:"),
-                    pconn->username);
-        dsend_packet_authentication_req(pconn, AUTH_LOGIN_FIRST, buffer);
-        pconn->server.status = AS_REQUESTING_OLD_PASS;
-        break;
-      case USER_DB_NOT_FOUND:
-        /* we couldn't find the user, he is new */
-        if (srvarg.auth_allow_newusers) {
-          sz_strlcpy(buffer, _("Enter a new password (and remember it)."));
-          dsend_packet_authentication_req(pconn, AUTH_NEWUSER_FIRST, buffer);
-          pconn->server.status = AS_REQUESTING_NEW_PASS;
-        } else {
-          reject_new_connection(_("This server allows only preregistered "
-                                  "users. Sorry."), pconn);
-          return FALSE;
-        }
-        break;
-      default:
-        assert(0);
-        break;
-      }
-      return TRUE;
-    }
-  }
-
-  sz_strlcpy(pconn->username, req->username);
-  establish_new_connection(pconn);
-  return TRUE;
-}
-
-/**************************************************************************
- sniff_packets calls this when pconn->server.authentication_stop == time(NULL)
- after an authentication fails
-**************************************************************************/
-void unfail_authentication(struct connection *pconn)
-{
-  assert(pconn->server.status == AS_FAILED);
-
-  if (pconn->server.authentication_tries >= MAX_AUTHENTICATION_TRIES) {
-    pconn->server.status = AS_NOT_ESTABLISHED;
-    reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
-    close_connection(pconn);
+    return authenticate_user(pconn, req->username);
   } 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);
-  }
-}
-
-/**************************************************************************
-  Receives a password from a client and verifies it.
-**************************************************************************/
-bool handle_authentication_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.authentication_tries++ >= MAX_AUTHENTICATION_TRIES) {
-        reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
-       return FALSE;
-      } else {
-        dsend_packet_authentication_req(pconn, AUTH_NEWUSER_RETRY,msg);
-       return TRUE;
-      }
-    }
-
-    /* the new password is good, create a database entry for
-     * this user; we establish the connection in handle_db_lookup */
-    sz_strlcpy(pconn->server.password, password);
-
-    switch(user_db_save(pconn)) {
-    case USER_DB_SUCCESS:
-      break;
-    case USER_DB_ERROR:
-      notify_conn(pconn->self, NULL, E_CONNECTION,
-                 _("Warning: There was an error in saving "
-                   "to the database. Continuing, but your "
-                   "stats will not be saved."));
-      freelog(LOG_ERROR, "Error writing to database for %s", pconn->username);
-      break;
-    default:
-      assert(0);
-    }
-
+    sz_strlcpy(pconn->username, req->username);
     establish_new_connection(pconn);
-  } else if (pconn->server.status == AS_REQUESTING_OLD_PASS) { 
-    if (userdb_check_password(pconn, password, strlen(password)) == 1) {
-      pconn->server.status = AS_ESTABLISHED;
-      establish_new_connection(pconn);
-    } else {
-      pconn->server.status = AS_FAILED;
-      pconn->server.authentication_tries++;
-      pconn->server.authentication_stop = time(NULL) + 
-                          auth_fail_period[pconn->server.authentication_tries];
-    }
-  } else {
-    freelog(LOG_VERBOSE, "%s is sending unrequested auth packets", 
-            pconn->username);
-    return FALSE;
+    return TRUE;
   }
-
-  return TRUE;
 }
 
 /**************************************************************************
-  see if the name qualifies as a guest login name
-**************************************************************************/
-static bool is_guest_name(const char *name)
-{
-  return (mystrncasecmp(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) && !find_conn_by_user(name)) {
-    return;
-  } 
-
-  /* next try bare guest name */
-  mystrlcpy(name, GUEST_NAME, MAX_LEN_NAME);
-  if (!find_conn_by_user(name)) {
-    return;
-  }
-
-  /* bare name is taken, append numbers */
-  for (i = 1; ; i++) {
-    my_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 (!find_conn_by_user(name)) {
-      break;
-    }
-  }
-}
-
-/**************************************************************************
- 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) {
-    my_snprintf(msg, MAX_LEN_MSG,
-                _("Your password is too short, the minimum length is %d. "
-                  "Try again."), MIN_PASSWORD_LEN);
-    return FALSE;
-  }
- 
-  my_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 (my_isupper(password[i])) {
-      num_caps++;
-    }
-    if (my_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;
-}
-
-/**************************************************************************
   High-level server stuff when connection to client is closed or lost.
   Reports loss to log, and to other players if the connection was a
   player.  Also removes player if in pregame, applies auto_toggle, and
Index: server/connecthand.h
===================================================================
--- server/connecthand.h        (revision 11340)
+++ server/connecthand.h        (working copy)
@@ -23,11 +23,12 @@
 struct packet_login_request;
 struct packet_server_join_req;
 
+void establish_new_connection(struct connection *pconn);
+void reject_new_connection(const char *msg, struct connection *pconn);
+
 bool handle_login_request(struct connection *pconn,
                           struct packet_server_join_req *req);
 
-void unfail_authentication(struct connection *pconn);
-
 void lost_connection_to_client(struct connection *pconn);
 
 void send_conn_info(struct conn_list *src, struct conn_list *dest);
@@ -36,6 +37,5 @@
 bool attach_connection_to_player(struct connection *pconn, 
                                  struct player *pplayer);
 bool unattach_connection_from_player(struct connection *pconn);
-bool handle_authentication_reply(struct connection *pc, char *password);
 
 #endif /* FC__CONNECTHAND_H */
Index: server/sernet.c
===================================================================
--- server/sernet.c     (revision 11340)
+++ server/sernet.c     (working copy)
@@ -74,6 +74,7 @@
 #include "support.h"
 #include "timing.h"
 
+#include "auth.h"
 #include "connecthand.h"
 #include "console.h"
 #include "meta.h"
@@ -546,10 +547,8 @@
 
     /* if we've waited long enough after a failure, respond to the client */
     conn_list_iterate(game.all_connections, pconn) {
-      if (pconn->server.status == AS_FAILED
-          && pconn->server.authentication_stop > 0
-          && time(NULL) >= pconn->server.authentication_stop) {
-        unfail_authentication(pconn);
+      if (srvarg.auth_enabled && pconn->server.status != AS_ESTABLISHED) {
+        process_authentication_status(pconn);
       }
     } conn_list_iterate_end
 
@@ -811,8 +810,8 @@
       pconn->is_server = TRUE;
       pconn->server.currently_processed_request_id = 0;
       pconn->server.last_request_id_seen = 0;
-      pconn->server.authentication_tries = 0;
-      pconn->server.authentication_stop = 0;
+      pconn->server.auth_tries = 0;
+      pconn->server.auth_settime = 0;
       pconn->server.status = AS_NOT_ESTABLISHED;
       pconn->server.ping_timers =
          fc_malloc(sizeof(*pconn->server.ping_timers));
Index: server/Makefile.am
===================================================================
--- server/Makefile.am  (revision 11340)
+++ server/Makefile.am  (working copy)
@@ -1,12 +1,12 @@
 ## Process this file with automake to produce Makefile.in
 
-SUBDIRS=       userdb generator scripting
+SUBDIRS=       generator scripting
 
 bin_PROGRAMS = civserver
 noinst_LIBRARIES = libcivserver.a
 AM_CPPFLAGS = \
        -I$(top_srcdir)/utility -I$(srcdir)/../common -I$(srcdir)/../ai \
-       -I../intl -I$(top_srcdir)/common/aicore -I$(srcdir)/userdb \
+       -I../intl -I$(top_srcdir)/common/aicore \
        -I$(srcdir)/generator -I$(srcdir)/scripting
 
 ## Above, note -I../intl instead of -I$(top_srdir/intl) is deliberate.
@@ -18,6 +18,8 @@
 libcivserver_a_SOURCES = \
                airgoto.c       \
                airgoto.h       \
+               auth.c          \
+               auth.h          \
                barbarian.c     \
                barbarian.h     \
                cityhand.c      \
@@ -88,7 +90,7 @@
       ../dependencies/lua/src/liblua.a \
       ../dependencies/lua/src/lib/liblualib.a \
       ../dependencies/tolua/libtolua.a \
-      ./generator/libgenerator.a $(USER_DB_DEP)
+      ./generator/libgenerator.a $(AUTH_DEPS)
 civserver_LDADD        = ../utility/libcivutility.a ../common/libcivcommon.a \
       ../ai/libcivai.a ../utility/libcivutility.a ./libcivserver.a @INTLLIBS@ \
       ../utility/libcivutility.a ../common/libcivcommon.a ../ai/libcivai.a \
@@ -98,7 +100,7 @@
       ../dependencies/lua/src/liblua.a \
       ../dependencies/lua/src/lib/liblualib.a \
       ../dependencies/tolua/libtolua.a \
-      $(USER_DB_LIB) $(SERVER_LIBS)
+      $(AUTH_LIBS) $(SERVER_LIBS)
 
 
 
Index: common/connection.h
===================================================================
--- common/connection.h (revision 11340)
+++ common/connection.h (working copy)
@@ -187,11 +187,12 @@
     struct timer_list *ping_timers;
    
     /* Holds number of tries for authentication from client. */
-    int authentication_tries;
+    int auth_tries;
 
-    /* the time that the server will reply after receiving an auth reply.
-     * this is used to throttle the connection. */
-    time_t authentication_stop;
+    /* the time that the server will respond after receiving an auth reply.
+     * this is used to throttle the connection. Also used to reject a 
+     * connection if we've waited too long for a password. */
+    time_t auth_settime;
 
     /* used to follow where the connection is in the authentication process */
     enum auth_status status;
Index: manual/Makefile.am
===================================================================
--- manual/Makefile.am  (revision 11340)
+++ manual/Makefile.am  (working copy)
@@ -1,7 +1,7 @@
 ## Process this file with automake to produce Makefile.in
 
 bin_PROGRAMS = civmanual
-AM_CPPFLAGS = -I$(top_srcdir)/server -I$(top_srcdir)/utility 
-I$(top_srcdir)/common -I$(top_srcdir)/ai -I../intl 
-I$(top_srcdir)/common/aicore -I$(top_srcdir)/server/userdb 
-I$(top_srcdir)/server/generator -I$(top_srcdir)/client 
-I$(top_srcdir)/client/include
+AM_CPPFLAGS = -I$(top_srcdir)/server -I$(top_srcdir)/utility 
-I$(top_srcdir)/common -I$(top_srcdir)/ai -I../intl 
-I$(top_srcdir)/common/aicore -I$(top_srcdir)/server/generator 
-I$(top_srcdir)/client -I$(top_srcdir)/client/include
 
 ## Above, note -I../intl instead of -I$(top_srcdir/intl) is deliberate.
 
@@ -13,22 +13,21 @@
 civmanual_DEPENDENCIES = ../utility/libcivutility.a ../common/libcivcommon.a \
       ../ai/libcivai.a ../utility/libcivutility.a ../server/libcivserver.a \
       ../utility/libcivutility.a ../common/aicore/libaicore.a \
-      ../server/userdb/libuserdb.a ../client/helpdata.o \
+      ../client/helpdata.o \
       ../server/scripting/libscripting.a \
       ../dependencies/lua/src/liblua.a \
       ../dependencies/lua/src/lib/liblualib.a \
       ../dependencies/tolua/libtolua.a \
-      ../server/generator/libgenerator.a
+      ../server/generator/libgenerator.a $(AUTH_DEPS)
 civmanual_LDADD        = ../utility/libcivutility.a ../common/libcivcommon.a \
       ../ai/libcivai.a ../utility/libcivutility.a ../server/libcivserver.a \
       @INTLLIBS@ ../client/helpdata.o \
       ../utility/libcivutility.a ../common/libcivcommon.a ../ai/libcivai.a \
       ../utility/libcivutility.a ../server/libcivserver.a \
-      ../server/userdb/libuserdb.a \
       ../utility/libcivutility.a ../common/aicore/libaicore.a \
       ../server/scripting/libscripting.a \
       ../dependencies/lua/src/liblua.a \
       ../dependencies/lua/src/lib/liblualib.a \
       ../dependencies/tolua/libtolua.a \
       ../server/generator/libgenerator.a \
-      $(SERVER_LIBS)
+      $(SERVER_LIBS) $(AUTH_LIBS)
Index: m4/auth.m4
===================================================================
--- m4/auth.m4  (revision 11345)
+++ m4/auth.m4  (working copy)
@@ -4,16 +4,33 @@
 
 AC_DEFUN([FC_CHECK_AUTH],
 [
-  # If the user calls --without-auth this will fail.
-  AC_ARG_WITH([auth],
-             [  --with-auth=lib         Specify authentication database 
library],
-             [USER_DB_LIB="$withval"], [USER_DB_LIB="yes"])
+  dnl  no=do not compile in authentication,  yes=compile in auth,  *=error
+  AC_ARG_ENABLE(auth, 
+  [  --enable-auth        do not compile in authentication],
+  [case "${enableval}" in
+    yes) auth=true ;;
+    no)  auth=false ;;
+    *)   AC_MSG_ERROR(bad value ${enableval} for --enable-auth) ;;
+   esac], [auth=false])
 
-  if test "$USER_DB_LIB" = "yes" || test "$USER_DB_LIB" = "no"; then
-    USER_DB_LIB="userdb/libuserdb.a"
+  if test x$auth = xtrue; then
+
+    AC_CHECK_HEADER(mysql/mysql.h, , 
+                    [AC_MSG_WARN([couldn't find mysql header: disabling auth]);
+                     auth=false])
+
+    AC_CHECK_LIB(mysqlclient, mysql_query, 
+                [AUTH_LIBS="-lmysqlclient $AUTH_LIBS";
+                  AUTH_DEPS="$AUTH_LIBS"],
+                 [AC_MSG_WARN([couldn't find mysql libs: disabling auth]);
+                  auth=false])
+
+    AC_SUBST(AUTH_LIBS)
+    AC_SUBST(AUTH_DEPS)
   fi
-  USER_DB_DEP="$USER_DB_LIB"
 
-  AC_SUBST(USER_DB_LIB)
-  AC_SUBST(USER_DB_DEP)
+  if test x$auth = xtrue; then
+    AC_DEFINE(HAVE_AUTH, 1, [can compile with authentication])
+  fi
+
 ])
Index: configure.ac
===================================================================
--- configure.ac        (revision 11345)
+++ configure.ac        (working copy)
@@ -671,7 +671,6 @@
          client/gui-stub/Makefile 
          server/Makefile 
          server/generator/Makefile
-         server/userdb/Makefile
          manual/Makefile
          intl/Makefile
          po/Makefile.in
Index: utility/Makefile.am
===================================================================
--- utility/Makefile.am (revision 11345)
+++ utility/Makefile.am (working copy)
@@ -48,4 +48,6 @@
                support.c       \
                support.h       \
                timing.c        \
-               timing.h
+               timing.h        \
+                md5.c           \
+                md5.h
Index: server/civserver.c
===================================================================
--- server/civserver.c  (revision 11345)
+++ server/civserver.c  (working copy)
@@ -122,12 +122,14 @@
        showhelp = TRUE;
        break;
       }
+#ifdef HAVE_AUTH
     } else if (is_option("--auth", argv[inx])) {
       srvarg.auth_enabled = TRUE;
     } else if (is_option("--Guests", argv[inx])) {
       srvarg.auth_allow_guests = TRUE;
     } else if (is_option("--Newusers", argv[inx])) {
       srvarg.auth_allow_newusers = TRUE;
+#endif
     } else if ((option = get_option("--Serverid", argv, &inx, argc))) {
       sz_strlcpy(srvarg.serverid, option);
     } else if ((option = get_option("--saves", argv, &inx, argc))) {
@@ -153,11 +155,13 @@
   if (showhelp) {
     fc_fprintf(stderr,
               _("Usage: %s [option ...]\nValid options are:\n"), argv[0]);
+#ifdef HAVE_AUTH
     fc_fprintf(stderr, _("  -a  --auth\t\tEnable server authentication.\n"));
     fc_fprintf(stderr, _("  -G  --Guests\t\tAllow guests to "
                         "login if auth is enabled.\n"));
     fc_fprintf(stderr, _("  -N  --Newusers\tAllow new users to "
                         "login if auth is enabled.\n"));
+#endif
     fc_fprintf(stderr, _("  -b  --bind ADDR\tListen for clients on ADDR\n"));
 #ifdef DEBUG
     fc_fprintf(stderr, _("  -d, --debug NUM\tSet debug log level (0 to 4,"
Index: server/srv_main.c
===================================================================
--- server/srv_main.c   (revision 11345)
+++ server/srv_main.c   (working copy)
@@ -68,6 +68,7 @@
 #include "timing.h"
 #include "version.h"
 
+#include "auth.h"
 #include "autoattack.h"
 #include "barbarian.h"
 #include "cityhand.h"
Index: server/connecthand.c
===================================================================
--- server/connecthand.c        (revision 11345)
+++ server/connecthand.c        (working copy)
@@ -28,7 +28,7 @@
 #include "support.h"
 #include "version.h"
 
-#include "user_db.h"
+#include "auth.h"
 #include "diplhand.h"
 #include "gamehand.h"
 #include "gamelog.h"
@@ -42,25 +42,6 @@
 
 #include "connecthand.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_AUTHENTICATION_TRIES 3
-
-/* after each wrong guess for a password, the server waits this
- * many seconds to reply to the client */
-static const int auth_fail_period[] = { 1, 1, 2, 3 };
-
-static void establish_new_connection(struct connection *pconn);
-static void reject_new_connection(const char *msg, struct connection *pconn);
-
-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);
-
 /**************************************************************************
   This is used when a new player joins a server, before the game
   has started.  If pconn is NULL, is an AI, else a client.
@@ -68,7 +49,7 @@
   N.B. this only attachs a connection to a player if 
        pconn->name == player->username
 **************************************************************************/
-static void establish_new_connection(struct connection *pconn)
+void establish_new_connection(struct connection *pconn)
 {
   struct conn_list *dest = &pconn->self;
   struct player *pplayer;
@@ -188,7 +169,7 @@
 /**************************************************************************
   send the rejection packet to the client.
 **************************************************************************/
-static void reject_new_connection(const char *msg, struct connection *pconn)
+void reject_new_connection(const char *msg, struct connection *pconn)
 {
   struct packet_server_join_reply packet;
 
@@ -280,253 +261,15 @@
   } conn_list_iterate_end;
 
   if (srvarg.auth_enabled) {
-    /* assign the client a unique guest name/reject if guests aren't allowed */
-    if (is_guest_name(req->username)) {
-      if (srvarg.auth_allow_guests) {
-        char old_guest_name[MAX_LEN_NAME];
-
-        sz_strlcpy(old_guest_name, req->username);
-        get_unique_guest_name(req->username);
-
-        if (strncmp(old_guest_name, req->username, MAX_LEN_NAME) != 0) {
-          notify_conn(&pconn->self, _("Warning: the guest name '%s' has been "
-                                      "taken, renaming to user '%s'."),
-                      old_guest_name, req->username);
-        }
-      } else {
-        reject_new_connection(_("Guests are not allowed on this server. "
-                                "Sorry."), pconn);
-        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 tmpname[MAX_LEN_NAME] = "\0";
-      char buffer[MAX_LEN_MSG];
-
-      sz_strlcpy(pconn->username, req->username);
-
-      switch(user_db_load(pconn)) {
-      case USER_DB_ERROR:
-        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);
-
-          freelog(LOG_ERROR, "Error reading database; connection -> guest");
-          notify_conn(&pconn->self, _("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);
-          return FALSE;
-        }
-        break;
-      case USER_DB_SUCCESS:
-        /* we found a user */
-        my_snprintf(buffer, sizeof(buffer), _("Enter password for %s:"),
-                    pconn->username);
-        dsend_packet_authentication_req(pconn, AUTH_LOGIN_FIRST, buffer);
-        pconn->server.status = AS_REQUESTING_OLD_PASS;
-        break;
-      case USER_DB_NOT_FOUND:
-        /* we couldn't find the user, he is new */
-        if (srvarg.auth_allow_newusers) {
-          sz_strlcpy(buffer, _("Enter a new password (and remember it)."));
-          dsend_packet_authentication_req(pconn, AUTH_NEWUSER_FIRST, buffer);
-          pconn->server.status = AS_REQUESTING_NEW_PASS;
-        } else {
-          reject_new_connection(_("This server allows only preregistered "
-                                  "users. Sorry."), pconn);
-          return FALSE;
-        }
-        break;
-      default:
-        assert(0);
-        break;
-      }
-      return TRUE;
-    }
-  }
-
-  sz_strlcpy(pconn->username, req->username);
-  establish_new_connection(pconn);
-  return TRUE;
-}
-
-/**************************************************************************
- sniff_packets calls this when pconn->server.authentication_stop == time(NULL)
- after an authentication fails
-**************************************************************************/
-void unfail_authentication(struct connection *pconn)
-{
-  assert(pconn->server.status == AS_FAILED);
-
-  if (pconn->server.authentication_tries >= MAX_AUTHENTICATION_TRIES) {
-    pconn->server.status = AS_NOT_ESTABLISHED;
-    reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
-    close_connection(pconn);
+    return authenticate_user(pconn, req->username);
   } 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);
-  }
-}
-
-/**************************************************************************
-  Receives a password from a client and verifies it.
-**************************************************************************/
-bool handle_authentication_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.authentication_tries++ >= MAX_AUTHENTICATION_TRIES) {
-        reject_new_connection(_("Sorry, too many wrong tries..."), pconn);
-       return FALSE;
-      } else {
-        dsend_packet_authentication_req(pconn, AUTH_NEWUSER_RETRY,msg);
-       return TRUE;
-      }
-    }
-
-    /* the new password is good, create a database entry for
-     * this user; we establish the connection in handle_db_lookup */
-    sz_strlcpy(pconn->server.password, password);
-
-    switch(user_db_save(pconn)) {
-    case USER_DB_SUCCESS:
-      break;
-    case USER_DB_ERROR:
-      notify_conn(&pconn->self, _("Warning: There was an error in saving "
-                                  "to the database. Continuing, but your "
-                                  "stats will not be saved."));
-      freelog(LOG_ERROR, "Error writing to database for %s", pconn->username);
-      break;
-    default:
-      assert(0);
-    }
-
+    sz_strlcpy(pconn->username, req->username);
     establish_new_connection(pconn);
-  } else if (pconn->server.status == AS_REQUESTING_OLD_PASS) { 
-    if (userdb_check_password(pconn, password, strlen(password)) == 1) {
-      pconn->server.status = AS_ESTABLISHED;
-      establish_new_connection(pconn);
-    } else {
-      pconn->server.status = AS_FAILED;
-      pconn->server.authentication_tries++;
-      pconn->server.authentication_stop = time(NULL) + 
-                          auth_fail_period[pconn->server.authentication_tries];
-    }
-  } else {
-    freelog(LOG_VERBOSE, "%s is sending unrequested auth packets", 
-            pconn->username);
-    return FALSE;
+    return TRUE;
   }
-
-  return TRUE;
 }
 
 /**************************************************************************
-  see if the name qualifies as a guest login name
-**************************************************************************/
-static bool is_guest_name(const char *name)
-{
-  return (mystrncasecmp(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) && !find_conn_by_user(name)) {
-    return;
-  } 
-
-  /* next try bare guest name */
-  mystrlcpy(name, GUEST_NAME, MAX_LEN_NAME);
-  if (!find_conn_by_user(name)) {
-    return;
-  }
-
-  /* bare name is taken, append numbers */
-  for (i = 1; ; i++) {
-    my_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 (!find_conn_by_user(name)) {
-      break;
-    }
-  }
-}
-
-/**************************************************************************
- 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) {
-    my_snprintf(msg, MAX_LEN_MSG,
-                _("Your password is too short, the minimum length is %d. "
-                  "Try again."), MIN_PASSWORD_LEN);
-    return FALSE;
-  }
- 
-  my_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 (my_isupper(password[i])) {
-      num_caps++;
-    }
-    if (my_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;
-}
-
-/**************************************************************************
   High-level server stuff when connection to client is closed or lost.
   Reports loss to log, and to other players if the connection was a
   player.  Also removes player if in pregame, applies auto_toggle, and
Index: server/connecthand.h
===================================================================
--- server/connecthand.h        (revision 11345)
+++ server/connecthand.h        (working copy)
@@ -23,11 +23,12 @@
 struct packet_login_request;
 struct packet_server_join_req;
 
+void establish_new_connection(struct connection *pconn);
+void reject_new_connection(const char *msg, struct connection *pconn);
+
 bool handle_login_request(struct connection *pconn,
                           struct packet_server_join_req *req);
 
-void unfail_authentication(struct connection *pconn);
-
 void lost_connection_to_client(struct connection *pconn);
 
 void send_conn_info(struct conn_list *src, struct conn_list *dest);
@@ -36,6 +37,5 @@
 bool attach_connection_to_player(struct connection *pconn, 
                                  struct player *pplayer);
 bool unattach_connection_from_player(struct connection *pconn);
-bool handle_authentication_reply(struct connection *pc, char *password);
 
 #endif /* FC__CONNECTHAND_H */
Index: server/sernet.c
===================================================================
--- server/sernet.c     (revision 11345)
+++ server/sernet.c     (working copy)
@@ -74,6 +74,7 @@
 #include "support.h"
 #include "timing.h"
 
+#include "auth.h"
 #include "connecthand.h"
 #include "console.h"
 #include "meta.h"
@@ -465,10 +466,8 @@
 
     /* if we've waited long enough after a failure, respond to the client */
     conn_list_iterate(game.all_connections, pconn) {
-      if (pconn->server.status == AS_FAILED
-          && pconn->server.authentication_stop > 0
-          && time(NULL) >= pconn->server.authentication_stop) {
-        unfail_authentication(pconn);
+      if (srvarg.auth_enabled && pconn->server.status != AS_ESTABLISHED) {
+        process_authentication_status(pconn);
       }
     } conn_list_iterate_end
 
@@ -764,8 +763,8 @@
       pconn->notify_of_writable_data = NULL;
       pconn->server.currently_processed_request_id = 0;
       pconn->server.last_request_id_seen = 0;
-      pconn->server.authentication_tries = 0;
-      pconn->server.authentication_stop = 0;
+      pconn->server.auth_tries = 0;
+      pconn->server.auth_settime = 0;
       pconn->server.status = AS_NOT_ESTABLISHED;
       pconn->server.ping_timers =
          fc_malloc(sizeof(*pconn->server.ping_timers));
Index: server/Makefile.am
===================================================================
--- server/Makefile.am  (revision 11345)
+++ server/Makefile.am  (working copy)
@@ -1,10 +1,10 @@
 ## Process this file with automake to produce Makefile.in
 
-SUBDIRS=       userdb generator
+SUBDIRS=       generator
 
 bin_PROGRAMS = civserver
 noinst_LIBRARIES = libcivserver.a
-AM_CPPFLAGS = -I$(top_srcdir)/utility -I$(srcdir)/../common -I$(srcdir)/../ai 
-I../intl -I$(top_srcdir)/common/aicore -I$(srcdir)/userdb -I$(srcdir)/generator
+AM_CPPFLAGS = -I$(top_srcdir)/utility -I$(srcdir)/../common -I$(srcdir)/../ai 
-I../intl -I$(top_srcdir)/common/aicore -I$(srcdir)/generator
 
 
 ## Above, note -I../intl instead of -I$(top_srdir/intl) is deliberate.
@@ -16,6 +16,8 @@
 libcivserver_a_SOURCES = \
                airgoto.c       \
                airgoto.h       \
+               auth.c          \
+               auth.h          \
                autoattack.c    \
                autoattack.h    \
                barbarian.c     \
@@ -83,11 +85,11 @@
 civserver_DEPENDENCIES = ../utility/libcivutility.a ../common/libcivcommon.a \
       ../ai/libcivai.a ../utility/libcivutility.a ./libcivserver.a \
       ../utility/libcivutility.a ../common/aicore/libaicore.a \
-      ./generator/libgenerator.a $(USER_DB_DEP)
+      ./generator/libgenerator.a $(AUTH_DEPS)
 civserver_LDADD        = ../utility/libcivutility.a ../common/libcivcommon.a \
       ../ai/libcivai.a ../utility/libcivutility.a ./libcivserver.a @INTLLIBS@ \
       ../utility/libcivutility.a ../common/libcivcommon.a ../ai/libcivai.a \
       ../utility/libcivutility.a ./libcivserver.a ../utility/libcivutility.a \
       ../common/aicore/libaicore.a ./generator/libgenerator.a \
-      $(USER_DB_LIB) $(SERVER_LIBS)
+      $(AUTH_LIBS) $(SERVER_LIBS)
 
Index: common/connection.h
===================================================================
--- common/connection.h (revision 11345)
+++ common/connection.h (working copy)
@@ -184,11 +184,12 @@
     struct timer_list *ping_timers;
    
     /* Holds number of tries for authentication from client. */
-    int authentication_tries;
+    int auth_tries;
 
-    /* the time that the server will reply after receiving an auth reply.
-     * this is used to throttle the connection. */
-    time_t authentication_stop;
+    /* the time that the server will respond after receiving an auth reply.
+     * this is used to throttle the connection. Also used to reject a 
+     * connection if we've waited too long for a password. */
+    time_t auth_settime;
 
     /* used to follow where the connection is in the authentication process */
     enum auth_status status;
Index: manual/Makefile.am
===================================================================
--- manual/Makefile.am  (revision 11345)
+++ manual/Makefile.am  (working copy)
@@ -1,7 +1,7 @@
 ## Process this file with automake to produce Makefile.in
 
 bin_PROGRAMS = civmanual
-AM_CPPFLAGS = -I$(top_srcdir)/server -I$(top_srcdir)/utility 
-I$(top_srcdir)/common -I$(top_srcdir)/ai -I../intl 
-I$(top_srcdir)/common/aicore -I$(top_srcdir)/server/userdb 
-I$(top_srcdir)/server/generator -I$(top_srcdir)/client 
-I$(top_srcdir)/client/include
+AM_CPPFLAGS = -I$(top_srcdir)/server -I$(top_srcdir)/utility 
-I$(top_srcdir)/common -I$(top_srcdir)/ai -I../intl 
-I$(top_srcdir)/common/aicore -I$(top_srcdir)/server/generator 
-I$(top_srcdir)/client -I$(top_srcdir)/client/include
 
 ## Above, note -I../intl instead of -I$(top_srcdir/intl) is deliberate.
 
@@ -13,15 +13,14 @@
 civmanual_DEPENDENCIES = ../utility/libcivutility.a ../common/libcivcommon.a \
       ../ai/libcivai.a ../utility/libcivutility.a ../server/libcivserver.a \
       ../utility/libcivutility.a ../common/aicore/libaicore.a \
-      ../server/userdb/libuserdb.a ../client/helpdata.o \
-      ../server/generator/libgenerator.a
+      ../client/helpdata.o \
+      ../server/generator/libgenerator.a $(AUTH_DEPS)
 civmanual_LDADD        = ../utility/libcivutility.a ../common/libcivcommon.a \
       ../ai/libcivai.a ../utility/libcivutility.a ../server/libcivserver.a \
       @INTLLIBS@ ../client/helpdata.o \
       ../utility/libcivutility.a ../common/libcivcommon.a ../ai/libcivai.a \
       ../utility/libcivutility.a ../server/libcivserver.a \
-      ../server/userdb/libuserdb.a \
       ../utility/libcivutility.a \
       ../common/aicore/libaicore.a \
       ../server/generator/libgenerator.a \
-      $(SERVER_LIBS)
+      $(SERVER_LIBS) $(AUTH_LIBS)
/**********************************************************************
 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 <config.h>
#endif

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

#ifdef HAVE_AUTH
  #include <mysql/mysql.h>
#endif

#include "auth.h"
#include "connection.h"
#include "fcintl.h"
#include "log.h"
#include "md5.h"
#include "packets.h"
#include "registry.h"
#include "shared.h"
#include "support.h"

#include "auth.h"
#include "connecthand.h"
#include "plrhand.h"
#include "sernet.h"
#include "srv_main.h"

/* where our mysql database is located and how to get to it */
#define HOST            "localhost"
#define USER            "anonymous"
#define PASSWORD        ""

/* the database where our table is located */
#define DATABASE        "test"

/* the tables where we will do our lookups and inserts.
 * the tables can be created with the following:
 *
 * CREATE TABLE auth (
 *   id int(11) NOT NULL auto_increment,
 *   name varchar(32) default NULL,
 *   password varchar(32) default NULL,
 *   email varchar(128) default NULL,
 *   createtime int(11) default NULL,
 *   accesstime int(11) default NULL,
 *   address varchar(255) default NULL,
 *   createaddress varchar(15) default NULL,
 *   logincount int(11) default '0',
 *   PRIMARY KEY  (id),
 *   UNIQUE KEY name (name)
 * ) TYPE=MyISAM;
 * 
 * CREATE TABLE loginlog (
 *   id int(11) NOT NULL auto_increment,
 *   name varchar(32) default NULL,
 *   logintime int(11) default NULL,
 *   address varchar(255) default NULL,
 *   succeed enum('S','F') default 'S',
 *   PRIMARY KEY  (id)
 * ) TYPE=MyISAM;
 *
 * N.B. if the tables are not of this format, then the select,insert,
 *      and update syntax in the auth_db_* functions below must be changed.
 */
#define AUTH_TABLE      "auth"
#define LOGIN_TABLE     "loginlog"

#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_good_password(const char *password, char *msg);

static bool authdb_check_password(struct connection *pconn,
                           const char *password, int len);
static enum authdb_status auth_db_load(struct connection *pconn);
static bool auth_db_save(struct connection *pconn);


/**************************************************
 The auth db statuses are:

 1: an error occurred, possibly we couldn't access the database file.
 2: we were able to successfully insert an entry. or we found the entry 
    we were searching for
 3: the user we were searching for was not found.
***************************************************/
enum authdb_status {
  AUTH_DB_ERROR,
  AUTH_DB_SUCCESS,
  AUTH_DB_NOT_FOUND
};

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

  if the connection is rejected right away, return FALSE, otherwise return TRUE
**************************************************************************/
bool authenticate_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 (strncmp(tmpname, username, MAX_LEN_NAME) != 0) {
        notify_conn(pconn->self, NULL, E_CONNECTION,
                    _("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);
      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];

    sz_strlcpy(pconn->username, username);

    switch(auth_db_load(pconn)) {
    case AUTH_DB_ERROR:
      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);

        freelog(LOG_ERROR, "Error reading database; connection -> guest");
        notify_conn(pconn->self, NULL, E_CONNECTION,
                    _("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);
        return FALSE;
      }
      break;
    case AUTH_DB_SUCCESS:
      /* we found a user */
      my_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;
      break;
    case AUTH_DB_NOT_FOUND:
      /* we couldn't find the user, he is new */
      if (srvarg.auth_allow_newusers) {
        sz_strlcpy(buffer, _("Enter a new password (and remember 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);
        return FALSE;
      }
      break;
    default:
      assert(0);
      break;
    }
    return TRUE;
  }

  return TRUE;
}

/**************************************************************************
  Receives a password from a client and verifies it.
**************************************************************************/
bool handle_authentication_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);
        return FALSE;
      } else {
        dsend_packet_authentication_req(pconn, AUTH_NEWUSER_RETRY, msg);
        return TRUE;
      }
    }

    /* the new password is good, create a database entry for
     * this user; we establish the connection in handle_db_lookup */
    sz_strlcpy(pconn->server.password, password);

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

    establish_new_connection(pconn);
  } else if (pconn->server.status == AS_REQUESTING_OLD_PASS) { 
    if (authdb_check_password(pconn, password, strlen(password)) == 1) {
      pconn->server.status = AS_ESTABLISHED;
      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 {
    freelog(LOG_VERBOSE, "%s is sending unrequested auth packets", 
            pconn->username);
    return FALSE;
  }

  return TRUE;
}

/**************************************************************************
 checks on where in the authentication process we are.
**************************************************************************/
void process_authentication_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);
        close_connection(pconn);
      } 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);
      close_connection(pconn);
    }
    break;
  case AS_ESTABLISHED:
    /* this better fail bigtime */
    assert(pconn->server.status != AS_ESTABLISHED);
    break;
  }
}

/**************************************************************************
  see if the name qualifies as a guest login name
**************************************************************************/
bool is_guest_name(const char *name)
{
  return (mystrncasecmp(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!
**************************************************************************/
void get_unique_guest_name(char *name)
{
  unsigned int i;

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

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

  /* bare name is taken, append numbers */
  for (i = 1; ; i++) {
    my_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 (!find_conn_by_user(name)) {
      break;
    }
  }
}

/**************************************************************************
 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) {
    my_snprintf(msg, MAX_LEN_MSG,
                _("Your password is too short, the minimum length is %d. "
                  "Try again."), MIN_PASSWORD_LEN);
    return FALSE;
  }
 
  my_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 (my_isupper(password[i])) {
      num_caps++;
    }
    if (my_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;
}


/**************************************************************************
  Check if the password with length len matches that given in 
  pconn->server.password.
***************************************************************************/
static bool authdb_check_password(struct connection *pconn, 
                          const char *password, int len)
{
#ifdef HAVE_AUTH
  bool ok = FALSE;
  char buffer[512] = "";
  unsigned char checksum[DIGEST_HEX_BYTES];
  MYSQL *sock, mysql;

  /* do the password checking right here */
  create_md5sum(password, len, checksum);
  ok = (strncmp(checksum, pconn->server.password, DIGEST_HEX_BYTES) == 0)
                                                              ? TRUE : FALSE;

  /* we don't really need the stuff below here to 
   * verify password, this is just logging */
  mysql_init(&mysql);

  /* attempt to connect to the server */
  if ((sock = mysql_real_connect(&mysql, HOST, USER, PASSWORD, 
                                 DATABASE, 0, NULL, 0))) {
    /* insert an entry into our log */
    my_snprintf(buffer, sizeof(buffer),
                    "insert into %s (name, logintime, address, succeed) "
                    "values ('%s',unix_timestamp(),'%s','%s')", LOGIN_TABLE,
                    pconn->username, pconn->server.ipaddr, ok ? "S" : "F");

    if (mysql_query(sock, buffer)) {
      freelog(LOG_ERROR, "check_pass insert loginlog failed for user: %s (%s)",
                         pconn->username, mysql_error(sock));
    }
    mysql_close(sock);
  } else {
    freelog(LOG_ERROR, "Can't connect to server! (%s)", mysql_error(&mysql));
  }

  return ok;
#else
  return TRUE;
#endif
}

/**************************************************************************
 Loads a user from the database.
**************************************************************************/
static enum authdb_status auth_db_load(struct connection *pconn)
{
#ifdef HAVE_AUTH
  char buffer[512] = "";
  int num_rows = 0;
  MYSQL *sock, mysql;
  MYSQL_RES *res;
  MYSQL_ROW row;
  
  mysql_init(&mysql);

  /* attempt to connect to the server */
  if (!(sock = mysql_real_connect(&mysql, HOST, USER, PASSWORD, DATABASE,
                                  0, NULL, 0))) {
    freelog(LOG_ERROR, "Can't connect to server! (%s)", mysql_error(&mysql));
    return AUTH_DB_ERROR;
  } 
  
  /* select the password from the entry */
  my_snprintf(buffer, sizeof(buffer), 
              "select password from %s where name = '%s'",
              AUTH_TABLE, pconn->username);

  if (mysql_query(sock, buffer)) {
    freelog(LOG_ERROR, "db_load query failed for user: %s (%s)",
                       pconn->username, mysql_error(sock));
    return AUTH_DB_ERROR;
  } 
  
  res = mysql_store_result(sock);
  num_rows = mysql_num_rows(res);
  
  /* if num_rows = 0, then we could find no such user */
  if (num_rows < 1) {
    mysql_free_result(res);
    mysql_close(sock);
    
    return AUTH_DB_NOT_FOUND;
  }
  
  /* if there are more than one row that matches this name, it's an error 
   * continue anyway though */
  if (num_rows > 1) {
    freelog(LOG_ERROR, "db_load query found multiple entries (%d) for user: %s",
                       num_rows, pconn->username);
  }

  /* if there are rows, then fetch them and use the first one */
  row = mysql_fetch_row(res);
  mystrlcpy(pconn->server.password, row[0], sizeof(pconn->server.password));
  mysql_free_result(res);

  /* update the access time for this user */
  memset(buffer, 0, sizeof(buffer));
  my_snprintf(buffer, sizeof(buffer),
                 "update %s set accesstime=unix_timestamp(), address='%s', "
                 "logincount=logincount+1 where strcmp(name, '%s') = 0",
                 AUTH_TABLE, pconn->server.ipaddr, pconn->username);

  if (mysql_query(sock, buffer)) {
    freelog(LOG_ERROR, "db_load update accesstime failed for user: %s (%s)",
                       pconn->username, mysql_error(sock));
  }

  mysql_close(sock);
#endif
  return AUTH_DB_SUCCESS;
}

/**************************************************************************
 Saves pconn fields to the database. If the username already exists, 
 replace the data.
**************************************************************************/
static bool auth_db_save(struct connection *pconn)
{
#ifdef HAVE_AUTH
  char buffer[1024] = "";
  MYSQL *sock, mysql;

  mysql_init(&mysql);

  /* attempt to connect to the server */
  if (!(sock = mysql_real_connect(&mysql, HOST, USER, PASSWORD, DATABASE,
                                  0, NULL, 0))) {
    freelog(LOG_ERROR, "Can't connect to server! (%s)", mysql_error(&mysql));
    return FALSE;
  }

  /* insert new user into table. we insert the following things: name
   * md5sum of the password, the creation time in seconds, the accesstime
   * also in seconds from 1970, the users address (twice) and the logincount */
  my_snprintf(buffer, sizeof(buffer),
          "insert into %s values "
          "(NULL, '%s', md5('%s'), NULL, unix_timestamp(), unix_timestamp(),"
          "'%s', '%s', 0)", AUTH_TABLE, pconn->username,
          pconn->server.password, pconn->server.ipaddr, pconn->server.ipaddr);

  if (mysql_query(sock, buffer)) {
    freelog(LOG_ERROR, "db_save insert failed for new user: %s (%s)",
                       pconn->username, mysql_error(sock));
    mysql_close(sock);
    return FALSE;
  }

  /* insert an entry into our log */
  memset(buffer, 0, sizeof(buffer));
  my_snprintf(buffer, sizeof(buffer),
                 "insert into %s (name, logintime, address, succeed) "
                 "values ('%s',unix_timestamp(),'%s', 'S')", LOGIN_TABLE,
                 pconn->username, pconn->server.ipaddr);

  if (mysql_query(sock, buffer)) {
    freelog(LOG_ERROR, "db_load insert loginlog failed for user: %s (%s)",
                       pconn->username, mysql_error(sock));
  }

  mysql_close(sock);
#endif
  return TRUE;
}
/**********************************************************************
 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.
***********************************************************************/
#ifndef FC__AUTH_H
#define FC__AUTH_H

#include "shared.h"

struct connection;

bool is_guest_name(const char *name);
void get_unique_guest_name(char *name);

bool authenticate_user(struct connection *pconn, char *username);
void process_authentication_status(struct connection *pconn);
bool handle_authentication_reply(struct connection *pc, char *password);

#endif /* FC__AUTH_H */
--- auth.c      2005-12-12 22:18:03.000000000 -0600
+++ auth-2.0.c  2005-12-12 23:25:12.000000000 -0600
@@ -132,7 +132,7 @@
       get_unique_guest_name(username);
 
       if (strncmp(tmpname, username, MAX_LEN_NAME) != 0) {
-        notify_conn(pconn->self, NULL, E_CONNECTION,
+        notify_conn(&pconn->self, 
                     _("Warning: the guest name '%s' has been "
                       "taken, renaming to user '%s'."), tmpname, username);
       }
@@ -158,7 +158,7 @@
         sz_strlcpy(pconn->username, tmpname);
 
         freelog(LOG_ERROR, "Error reading database; connection -> guest");
-        notify_conn(pconn->self, NULL, E_CONNECTION,
+        notify_conn(&pconn->self, 
                     _("There was an error reading the user "
                       "database, logging in as guest connection '%s'."), 
                     pconn->username);
@@ -226,7 +226,7 @@
     sz_strlcpy(pconn->server.password, password);
 
     if (!auth_db_save(pconn)) {
-      notify_conn(pconn->self, NULL, E_CONNECTION,
+      notify_conn(&pconn->self, 
                  _("Warning: There was an error in saving to the database. "
                     "Continuing, but your stats will not be saved."));
       freelog(LOG_ERROR, "Error writing to database for: %s", pconn->username);

[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] (PR#14851) remove userdb, add mysql to server for authentication, Mike Kaufman <=