--- contrib/mod_sqlpw.c.old	Sun Feb 11 19:47:34 2001
+++ contrib/mod_sqlpw.c	Sun Feb 11 19:45:37 2001
@@ -0,0 +1,1020 @@
+/*
+ * ProFTPD: mod_sql -- SQL frontend and user interface.
+ * Time-stamp: <1999-10-04 03:58:01 root>
+ * Copyright (c) 1998-1999 Johnie Ingram.
+ *  
+ * 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 of the License, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define MOD_SQL_VERSION "mod_sqlpw/2.0"
+
+/* This is mod_sqlpw, contrib software for proftpd 1.2.0pre3 and above.
+   For more information contact Johnie Ingram <johnie@netgod.net>.
+
+   History Log:
+
+   * 2000-03-16: Added SQLKeyField and SQLKey directive, which
+     should allow authentication of different logins for different
+     VirtualHosts to be in the same database table.
+     Jake Buchholz <jake@execpc.com>
+
+   * 1999-09-19: v2.0: Backend directives split into mod_mysql.
+     Runtime API added.  Username/passwords now escaped for SQL.
+
+   * 1999-09-16: v1.0: Added documentation, made directives more
+     generic ("MySQL" -> "SQL" except for MySQLInfo).  Dir part of
+     SQLLogDirs finally made optional.
+
+   * 1999-06-01: v0.3: fixed segfault, changed 'strncmp' typo which
+     should have been copy.  Made uid/gid support optional.
+   
+   * 1999-05-03: v0.2: Removed dead code, fix bug with interaction
+     with anon-user support.  Added MySQLLogHits and MySQLHomedir
+     directives.  Fixed atoi() invocation that could segfault.
+     Copious debugging code added.
+
+   * 1999-04-09: v0.1: Initial attempt (mod_mysql).
+
+*/
+
+#include "conf.h"
+#ifdef HAVE_CRYPT_H
+#include <crypt.h>
+#endif
+
+#include <mysql.h>
+
+/* *INDENT-OFF* */
+
+/* The Debian defintion of nobody/nogroup, minus 1 (used in getgwnam).  */
+#define MOD_SQL_MAGIC_USER 65533
+#define MOD_SQL_MAGIC_GROUP 65533
+
+/* A uid or gid less than this is mapped to the magic numbers above
+   instead of simply rejected (which is arguably better, hmm.) */
+#define MOD_SQL_MIN_ID 999
+
+#define MOD_SQL_MAGIC_SHELL "/bin/sh"
+
+/* Maximum username field to expect, etc. */
+#define ARBITRARY_MAX                   128
+
+static struct {
+  char user [ARBITRARY_MAX];
+  struct passwd pw;           /* Scratch space for the getpwnam call. */
+  char *pass;                 /* The password from db. */
+
+  char *homedir;              /* Set when getpwnam is called. */
+  int pwnamed;                /* Set when getpwnam is called. */
+  int sqled;                  /* Set if sql auth was used. */
+
+  char *sql_usertable;        /* CREATE TABLE users ( */
+  char *sql_userid;           /* userid varchar(50) NOT NULL, */
+  char *sql_passwd;           /* passwd varchar(15), */
+
+  char *sql_uid;              /* uid int(5), */
+  char *sql_gid;              /* gid int(5), */
+
+  char *sql_fstor;            /* fstor int(11) NOT NULL DEFAULT '0', */
+  char *sql_fretr;            /* fretr int(11) NOT NULL DEFAULT '0', */
+  char *sql_bstor;            /* bstor int(11) NOT NULL DEFAULT '0', */
+  char *sql_bretr;            /* bretr int(11) NOT NULL DEFAULT '0', */
+
+  char *sql_fhdir;            /* fhdir varchar(255), */
+
+  char *sql_fhost;            /* fhost varchar(50), */
+  char *sql_faddr;            /* faddr char(15), */
+  char *sql_ftime;            /* ftime timestamp, */
+
+  char *sql_fcdir;            /* fcdir varchar(255), */
+
+  char *sql_frate;            /* frate int(11) NOT NULL DEFAULT '5', */
+  char *sql_fcred;            /* fcred int(2) NOT NULL DEFAULT '15', */
+  char *sql_brate;            /* brate int(11) NOT NULL DEFAULT '5', */
+  char *sql_bcred;            /* bcred int(2) NOT NULL DEFAULT '150000', */
+
+  char *sql_flogs;            /* flogs int(11) NOT NULL DEFAULT '0', */
+
+  char *sql_hittable;
+  char *sql_dir;
+  char *sql_filename;
+  char *sql_hits;
+
+  char *sql_keyfld;          /* table column name that stores the key */
+  char *sql_key;             /* the key to match db table entries against */
+
+  char *loginmsg;
+  int ok;
+
+} g;
+
+/* *INDENT-ON* */
+
+/* **************************************************************** */
+
+/* Functions make_cmd and dispatch liberally stolen from auth.c. */
+
+static cmd_rec *
+_make_cmd (pool * cp, int argc, ...)
+{
+  va_list args;
+  cmd_rec *c;
+  int i;
+
+  c = pcalloc (cp, sizeof (cmd_rec));
+  c->argc = argc;
+  c->symtable_index = -1;
+
+  c->argv = pcalloc (cp, sizeof (void *) * (argc + 1));
+  c->argv[0] = MOD_SQL_VERSION;
+  va_start (args, argc);
+  for (i = 0; i < argc; i++)
+    c->argv[i + 1] = (void *) va_arg (args, char *);
+  va_end (args);
+
+  return c;
+}
+
+static modret_t *
+_dispatch_sql (cmd_rec * cmd, char *match)
+{
+  authtable *m;
+  modret_t *mr = NULL;
+
+  m = mod_find_auth_symbol (match, &cmd->symtable_index, NULL);
+  while (m)
+    {
+      mr = call_module_auth (m->m, m->handler, cmd);
+      if (MODRET_ISHANDLED (mr) || MODRET_ISERROR (mr))
+	break;
+      m = mod_find_auth_symbol (match, &cmd->symtable_index, m);
+    }
+  if (MODRET_ISERROR (mr))
+    log_debug (DEBUG0, "Aiee! sql internal!  %s", MODRET_ERRMSG (mr));
+  return mr;
+}
+
+MODRET
+modsql_open (cmd_rec * cmd)
+{
+  cmd_rec *c;
+  modret_t *mr;
+
+  c = _make_cmd (cmd ? cmd->tmp_pool : permanent_pool, 0);
+  mr = _dispatch_sql (c, "dbd_open");
+  if (c->tmp_pool)
+    destroy_pool (c->tmp_pool);
+  return mr;
+}
+
+MODRET
+modsql_close (cmd_rec * cmd)
+{
+  cmd_rec *c;
+  modret_t *mr;
+
+  c = _make_cmd (cmd ? cmd->tmp_pool : permanent_pool, 0);
+  mr = _dispatch_sql (c, "dbd_close");
+  if (c->tmp_pool)
+    destroy_pool (c->tmp_pool);
+  return mr;
+}
+
+MODRET
+modsql_update (cmd_rec * cmd, const char *query)
+{
+  cmd_rec *c;
+  modret_t *mr;
+
+  c = _make_cmd (cmd->tmp_pool, 1, query);
+  mr = _dispatch_sql (c, "dbd_update");
+  if (c->tmp_pool)
+    destroy_pool (c->tmp_pool);
+  return mr;
+}
+
+MODRET
+modsql_select (cmd_rec * cmd, const char *query)
+{
+  cmd_rec *c;
+  modret_t *mr;
+
+  c = _make_cmd (cmd->tmp_pool, 1, query);
+  mr = _dispatch_sql (c, "dbd_select");
+  if (c->tmp_pool)
+    destroy_pool (c->tmp_pool);
+  return mr;
+}
+
+MODRET
+modsql_queryuser (cmd_rec * cmd, const char *user, const char *query,
+		  int update)
+{
+  char *realquery;
+
+  if ((update) && (!g.sql_keyfld))
+    realquery = pstrcat (cmd->tmp_pool, "update ", g.sql_usertable,
+			 " set ", query, " where ", g.sql_userid, " = '",
+			 user, "'", NULL);
+  else if ((update) && (g.sql_keyfld))
+    realquery = pstrcat (cmd->tmp_pool, "update ", g.sql_usertable,
+			 " set ", query, " where ", g.sql_userid, " = '",
+			 user, "' and ", g.sql_keyfld, " = '",
+			 g.sql_key, "'", NULL);
+  else if ((!update) && (!g.sql_keyfld))
+    realquery = pstrcat (cmd->tmp_pool, "select ", query, " from ",
+			 g.sql_usertable, " where ", g.sql_userid,
+			 " = '", user, "'", NULL);
+  else
+    realquery = pstrcat (cmd->tmp_pool, "select ", query, " from ",
+			 g.sql_usertable, " where ", g.sql_userid,
+			 " = '", user, "' and ", g.sql_keyfld, " = '",
+			 g.sql_key, "'", NULL);
+  return (update) ? modsql_update (cmd, realquery)
+    : modsql_select (cmd, realquery);
+}
+
+/* **************************************************************** */
+
+static char *
+_uservar (cmd_rec * cmd, const char *user, const char *var)
+{
+  cmd_rec *c;
+  modret_t *mr;
+  char *query;
+  char **data;
+
+  if (!g.sql_keyfld)
+    query = pstrcat (cmd->tmp_pool, "select ", var, " from ",
+		   g.sql_usertable, " where ", g.sql_userid,
+		   " = '", user, "'", NULL);
+  else
+    query = pstrcat (cmd->tmp_pool, "select ", var, " from ",
+		   g.sql_usertable, " where ", g.sql_userid,
+		   " = '", user, "' and ", g.sql_keyfld, " = '",
+		   g.sql_key, "'", NULL);
+  c = _make_cmd (cmd->tmp_pool, 1, query);
+
+  mr = _dispatch_sql (c, "dbd_select");
+
+  if (c->tmp_pool)
+    destroy_pool (c->tmp_pool);
+  if (MODRET_ISHANDLED (mr))
+    {
+      data = mr->data;
+      return (data) ? data[0] : NULL;
+    }
+
+  return NULL;
+}
+
+/* Note: This function is called once by the master proftpd process
+   before it forks, so thankfully the homedir field is only set in the
+   child_init(). */
+
+MODRET
+auth_cmd_getpwnam (cmd_rec * cmd)
+{
+  const char *homedir;
+
+  if (!g.sql_fhdir && !g.homedir)
+    return DECLINED (cmd);
+
+  /* Only try this for the USER command (is also called twice after
+     PASS for some reason.  [update: 1 is dir_realpath/defroot related.] */
+  if (!g.homedir && !g.pwnamed++)
+    {
+      if ((homedir = _uservar (cmd, cmd->argv[0], g.sql_fhdir)))
+	g.homedir = pstrdup (session.pool, homedir);
+    }
+  if (!g.homedir)
+    return DECLINED (cmd);
+
+  if (!g.pw.pw_name)
+    {
+      g.pw.pw_name = pstrdup(session.pool, cmd->argv[0]);
+      if (g.sql_uid)
+	g.pw.pw_uid = atoi (_uservar (cmd, cmd->argv[0], g.sql_uid) ? : "0");
+      if (g.pw.pw_uid < MOD_SQL_MIN_ID)
+	g.pw.pw_uid = MOD_SQL_MAGIC_USER;
+      if (g.sql_gid)
+	g.pw.pw_gid = atoi (_uservar (cmd, cmd->argv[0], g.sql_gid) ? : "0");
+      if (g.pw.pw_gid < MOD_SQL_MIN_ID)
+	g.pw.pw_gid = MOD_SQL_MAGIC_GROUP;
+      g.pw.pw_shell = MOD_SQL_MAGIC_SHELL;
+      g.pw.pw_dir = (char *) g.homedir;
+      log_debug (DEBUG3, "sqlpw: user \"%s\" (%i/%i) for %s",
+		 cmd->argv[0], g.pw.pw_uid, g.pw.pw_gid, g.pw.pw_dir);
+
+      /* Copy username so proftpd anon handling won't confuse the issue. */
+
+      /* FIXME: unnecessary mysqlism */
+      mysql_escape_string (g.user, g.pw.pw_name, strlen (g.pw.pw_name));
+      g.user[ARBITRARY_MAX - 1] = 0;
+    }
+
+  return mod_create_data (cmd, &g.pw);
+}
+
+/* Returns 1 on successful match, 0 unsuccessful match, -1 on error. */
+
+static int
+_checkpass (cmd_rec * cmd, const char *user, const char *pass)
+{
+  int success = 0;
+  char *query;
+  int emptyok, cplain, ccrypt;
+  char **row;
+  MODRET mr;
+
+  emptyok = get_param_int (CURRENT_CONF, "SQLEmptyPasswords", FALSE);
+  cplain = get_param_int (CURRENT_CONF, "SQLPlaintextPasswords", FALSE);
+  ccrypt = get_param_int (CURRENT_CONF, "SQLEncryptedPasswords", FALSE);
+
+  if (!g.sql_keyfld)
+    query = pstrcat (cmd->tmp_pool, "select ", g.sql_passwd, " from ",
+		   g.sql_usertable, " where ", g.sql_userid,
+		   " = '", user, "'", " limit 2", NULL);
+  else
+    query = pstrcat (cmd->tmp_pool, "select ", g.sql_passwd, " from ",
+		   g.sql_usertable, " where ", g.sql_userid,
+		   " = '", user, "' and ", g.sql_keyfld, " = '",
+		   g.sql_key, "' limit 2", NULL);
+  mr = modsql_select (cmd, query);
+  if (!(MODRET_HASDATA (mr)))
+    return -1;
+
+  row = mr->data;
+  if (row[0] == 0)
+    return 0;
+
+  if (!row[0])
+    {
+      log_debug (DEBUG4, "sqlpw: %s auth declined for NULL pass", user);
+      return 0;
+    }
+
+  if (row[1])
+    {
+      log_debug (DEBUG3, "sqlpw: %s pass result was not unique", user);
+      return -1;
+    }
+
+  if (emptyok == TRUE && !strlen (row[0]))
+    {
+      log_debug (DEBUG4, "sqlpw: warning: %s has empty password", user);
+      success = 1;
+    }
+
+  if (!success && cplain == TRUE && !strncasecmp (row[0], pass, 10))
+    {
+      success = 1;
+    }
+
+  /* Deliberate: ccrypt same if TRUE or -1 (unspecified). */
+  if (!success && ccrypt)
+    {
+      if (!strcmp (query = (char *) crypt (pass, row[0]), row[0]))
+	success = 1;
+    }
+
+  if (!success)
+    log_debug (DEBUG5, "sqlpw: %s auth failed: '%s' != '%s'",
+	       user, pass, row ? row[0] : "");
+  return success;
+}
+
+MODRET
+auth_cmd_auth (cmd_rec * cmd)
+{
+  char *user, *pass;
+  int return_type;
+  int retval = AUTH_NOPWD;
+
+  if (!g.sql_passwd || !g.homedir)
+    return DECLINED (cmd);
+
+  /* Figure out our default return style: Whether or not SQL should
+   * allow other auth modules a shot at this user or not is controlled
+   * by the parameter "SQLAuthoritative".  Like mod_pam this
+   * defaults to no.  */
+  if((return_type = get_param_int (CURRENT_CONF,
+				   "SQLAuthoritative", FALSE)) == -1) {
+    return_type = 0;
+  }
+
+  /* Just in case... */
+  if (cmd->argc != 2)
+    return return_type ? ERROR (cmd) : DECLINED (cmd);
+  if ((user = cmd->argv[0]) == NULL)
+    return return_type ? ERROR (cmd) : DECLINED (cmd);
+  if ((pass = cmd->argv[1]) == NULL)
+    return return_type ? ERROR (cmd) : DECLINED (cmd);
+
+  if (!g.pass
+      && get_param_int (CURRENT_CONF, "SQLEmptyPasswords", FALSE) == 2)
+    {
+      char *query;
+      char passbuf[ARBITRARY_MAX] = {'\0'};
+
+      /* FIXME: unnecessary mysqlism */
+      mysql_escape_string (passbuf, cmd->argv[1], strlen (cmd->argv[1]));
+      g.user[ARBITRARY_MAX - 1] = 0;
+
+      if (!g.sql_keyfld)
+        query = pstrcat (cmd->tmp_pool, "update ", g.sql_usertable,
+		       " set ", g.sql_passwd, " = '", passbuf,
+		       "' where ", g.sql_userid, " = '", g.user, "'", 0);
+      else
+        query = pstrcat (cmd->tmp_pool, "update ", g.sql_usertable,
+		       " set ", g.sql_passwd, " = '", passbuf,
+		       "' where ", g.sql_userid, " = '", g.user, "' and ",
+		       g.sql_keyfld, " = '", g.sql_key, "'", 0);
+      log_debug (DEBUG3, "sqlpw: %s NULL pass set to '%s'", user,
+		 cmd->argv[1], query);
+      modsql_update (cmd, query);
+    }
+
+  if (_checkpass (cmd, g.user, pass) != 1)
+    return return_type ? ERROR_INT (cmd, retval) : DECLINED (cmd);
+
+  g.sqled++;
+  return HANDLED (cmd);
+}
+
+MODRET
+auth_cmd_getstats (cmd_rec * cmd)
+{
+  MODRET mr;
+  char *query;
+
+  if (g.sql_fstor)
+    {
+      if (!g.sql_keyfld)
+        query = pstrcat (cmd->tmp_pool, "select ", g.sql_fstor, ", ",
+		       g.sql_fretr, ", ", g.sql_bstor, ", ",
+		       g.sql_bretr, " from ", g.sql_usertable, " where ",
+		       g.sql_userid, " = '", g.user, "'", NULL);
+      else
+        query = pstrcat (cmd->tmp_pool, "select ", g.sql_fstor, ", ",
+		       g.sql_fretr, ", ", g.sql_bstor, ", ",
+		       g.sql_bretr, " from ", g.sql_usertable, " where ",
+		       g.sql_userid, " = '", g.user, "' and ", g.sql_keyfld,
+		       " = '", g.sql_key, "'", NULL);
+      mr = modsql_select (cmd, query);
+      if (MODRET_HASDATA (mr))
+	return mr;
+    }
+  return DECLINED (cmd);
+}
+
+MODRET
+auth_cmd_getratio (cmd_rec * cmd)
+{
+  MODRET mr;
+  char *query;
+
+  if (g.sql_frate)
+    {
+      if (!g.sql_keyfld)
+        query = pstrcat (cmd->tmp_pool, "select ", g.sql_frate, ", ",
+		       g.sql_fcred, ", ", g.sql_brate, ", ",
+		       g.sql_bcred, " from ", g.sql_usertable, " where ",
+		       g.sql_userid, " = '", g.user, "'", NULL);
+      else
+        query = pstrcat (cmd->tmp_pool, "select ", g.sql_frate, ", ",
+		       g.sql_fcred, ", ", g.sql_brate, ", ",
+		       g.sql_bcred, " from ", g.sql_usertable, " where ",
+		       g.sql_userid, " = '", g.user, "' and ", g.sql_keyfld,
+		       " = '", g.sql_key, "'", NULL);
+      mr = modsql_select (cmd, query);
+      if (MODRET_HASDATA (mr))
+	return mr;
+    }
+  return DECLINED (cmd);
+}
+
+static authtable sqlpw_authtab[] = {
+
+  {0, "auth", auth_cmd_auth},
+  {0, "getpwnam", auth_cmd_getpwnam},
+  {0, "getstats", auth_cmd_getstats},
+  {0, "getratio", auth_cmd_getratio},
+  {0, NULL, NULL}
+};
+
+/* **************************************************************** */
+
+static void
+_setstats (cmd_rec * cmd, int fstor, int fretr, int bstor, int bretr)
+{
+  char query[ARBITRARY_MAX] = {'\0'};
+  snprintf (query, sizeof (query),
+	    "%s = %s + %i, %s = %s + %i, %s = %s + %i, %s = %s + %i",
+	    g.sql_fstor, g.sql_fstor, fstor,
+	    g.sql_fretr, g.sql_fretr, fretr,
+	    g.sql_bstor, g.sql_bstor, bstor, g.sql_bretr, g.sql_bretr, bretr);
+  modsql_queryuser (cmd, g.user, query, TRUE);
+}
+
+MODRET
+pre_cmd_quit (cmd_rec * cmd)
+{
+  if (g.ok)
+    modsql_close (cmd);
+  return DECLINED (cmd);
+}
+
+MODRET
+cmd_user (cmd_rec * cmd)
+{
+  if (!g.user[0])
+    sstrncpy (g.user, cmd->argv[1], ARBITRARY_MAX);
+
+  if (g.sql_passwd)
+    {
+      g.pass = (char *) _uservar (cmd, cmd->argv[1], g.sql_passwd);
+      if (!g.pass
+	  && get_param_int (CURRENT_CONF, "SQLEmptyPasswords", FALSE) == 2)
+	if (_uservar (cmd, cmd->argv[1], "1"))
+	  add_response (R_331, "Changing password for %s -- "
+			"this password will be saved.", cmd->argv[1]);
+    }
+  return DECLINED (cmd);
+}
+
+MODRET
+cmd_pass (cmd_rec * cmd)
+{
+  if (g.sql_passwd && !g.pass
+      && get_param_int (CURRENT_CONF, "SQLEmptyPasswords", FALSE) == 2)
+    add_response (R_230, "\"%s\" is the new user \"%s\" password.",
+		  cmd->argv[1], g.user);
+
+  /* This is called before the disconnect() in log_cmd_pass. */
+  if (g.sql_fcdir)
+    {
+      const char *d = _uservar (cmd, g.user, g.sql_fcdir);
+      if (d)
+	add_response (R_230, "\"%s\" was last directory.", d);
+    }
+
+  return DECLINED (cmd);
+}
+
+MODRET
+post_cmd_pass (cmd_rec * cmd)
+{
+  if (g.sql_passwd && g.sqled)
+    session.anon_user = session.user = (char *) g.user;
+  return DECLINED (cmd);
+}
+
+MODRET
+post_cmd_stor (cmd_rec * cmd)
+{
+  if (g.sql_fstor)
+    _setstats (cmd, 1, 0, session.xfer.total_bytes, 0);
+  return DECLINED (cmd);
+}
+
+MODRET
+cmd_retr (cmd_rec * cmd)
+{
+  int i;
+  char *path, *filename, *query;
+  if (g.sql_hittable)
+    {
+      path = dir_realpath (cmd->tmp_pool, cmd->argv[1]);
+      if (g.sql_dir && g.sql_dir[0])
+	{
+	  for (i = strlen (path), filename = path + i;
+	       *filename != '/' && i > 1; i--)
+	    filename--;
+	  *filename++ = 0;
+	  query = pstrcat (cmd->tmp_pool, "update ", g.sql_hittable,
+			   " set ", g.sql_hits, " = ", g.sql_hits,
+			   " + 1 where ", g.sql_dir, " = '", ++path,
+			   "' and ", g.sql_filename, " = '", filename,
+			   "'", 0);
+	}
+      else
+	query = pstrcat (cmd->tmp_pool, "update ", g.sql_hittable,
+			 " set ", g.sql_hits, " = ", g.sql_hits,
+			 " + 1 where ", g.sql_filename, " = '", path, "'", 0);
+      modsql_update (cmd, query);
+    }
+  return DECLINED (cmd);
+}
+
+MODRET
+post_cmd_retr (cmd_rec * cmd)
+{
+  if (g.sql_fretr)
+    _setstats (cmd, 0, 1, 0, session.xfer.total_bytes);
+  return DECLINED (cmd);
+}
+
+MODRET
+log_cmd_pass (cmd_rec * cmd)
+{
+  char *query;
+
+  if (g.sql_fhost)
+    {
+      query = pstrcat (cmd->tmp_pool, g.sql_fhost, " = '",
+		       session.c->remote_name, "', ", g.sql_faddr,
+		       " = '", inet_ntoa (*session.c->remote_ipaddr),
+		       "', ", g.sql_ftime, " = now()", NULL);
+      modsql_queryuser (cmd, g.user, query, TRUE);
+    }
+  if (g.sql_flogs)
+    {
+      query = pstrcat (cmd->tmp_pool, g.sql_flogs, " = ", g.sql_flogs,
+		       " + 1", NULL);
+      modsql_queryuser (cmd, g.user, query, TRUE);
+    }
+
+  /* Autononpersistence: disconnect now if no other feature is being used. */
+  if (!g.sql_fstor && !g.sql_fcdir && !g.sql_hittable)
+    modsql_close (cmd);
+  return DECLINED (cmd);
+}
+
+MODRET
+log_cmd_cwd (cmd_rec * cmd)
+{
+  if (g.sql_fcdir)
+    {
+      char *query = pstrcat (cmd->tmp_pool, g.sql_fcdir, " = '",
+			     session.cwd, "'", NULL);
+      modsql_queryuser (cmd, g.user, query, TRUE);
+    }
+  return DECLINED (cmd);
+}
+
+static cmdtable sqlpw_cmdtab[] = {
+/* *INDENT-OFF* */
+
+  { PRE_CMD,  C_QUIT,	G_NONE, pre_cmd_quit, 	FALSE, FALSE },
+  { CMD,      C_USER,	G_NONE, cmd_user, 	FALSE, FALSE },
+  { CMD,      C_PASS,	G_NONE, cmd_pass, 	FALSE, FALSE },
+  { POST_CMD, C_PASS,	G_NONE, post_cmd_pass, 	FALSE, FALSE },
+  { POST_CMD, C_STOR,	G_NONE, post_cmd_stor, 	FALSE, FALSE },
+  { POST_CMD, C_RETR,	G_NONE, post_cmd_retr, 	FALSE, FALSE },
+  { CMD,      C_RETR,	G_NONE, cmd_retr, 	FALSE, FALSE },
+  { LOG_CMD,  C_PASS,	G_NONE, log_cmd_pass, 	FALSE, FALSE },
+  { LOG_CMD,  C_CWD,	G_NONE, log_cmd_cwd, 	FALSE, FALSE },
+  { LOG_CMD,  C_CDUP,	G_NONE, log_cmd_cwd, 	FALSE, FALSE },
+
+  { 0,	      NULL }
+
+/* *INDENT-ON* */
+};
+
+/* **************************************************************** */
+
+MODRET
+set_sqlloghosts (cmd_rec * cmd)
+{
+  int b;
+
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+  switch (cmd->argc - 1)
+    {
+    default:
+      CONF_ERROR (cmd, "requires a boolean or 3 field names: "
+		  "fhost faddr ftime");
+    case 1:
+      if ((b = get_boolean (cmd, 1)) == -1)
+	CONF_ERROR (cmd, "requires a boolean or 3 field names: "
+		    "fhost faddr ftime");
+      if (b)
+	add_config_param_str ("SQLLogHosts", 3, "fhost", "faddr", "ftime");
+      break;
+
+    case 3:
+      add_config_param_str ("SQLLogHosts", 3,
+			    (void *) cmd->argv[1], (void *) cmd->argv[2],
+			    (void *) cmd->argv[3]);
+    }
+  return HANDLED (cmd);
+}
+
+MODRET
+set_sqllogstats (cmd_rec * cmd)
+{
+  int b;
+
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+  switch (cmd->argc - 1)
+    {
+    default:
+      CONF_ERROR (cmd, "requires a boolean or 4 field names: "
+		  "fstor fretr bstor bretr");
+    case 1:
+      if ((b = get_boolean (cmd, 1)) == -1)
+	CONF_ERROR (cmd, "requires a boolean or 4 field names: "
+		    "fstor fretr bstor bretr");
+      if (b)
+	add_config_param_str ("SQLLogStats", 4,
+			      "fstor", "fretr", "bstor", "bretr");
+      break;
+
+    case 4:
+      add_config_param_str ("SQLLogStats", 4,
+			    (void *) cmd->argv[1], (void *) cmd->argv[2],
+			    (void *) cmd->argv[3], (void *) cmd->argv[4]);
+    }
+  return HANDLED (cmd);
+}
+
+MODRET
+set_sqlloghits (cmd_rec * cmd)
+{
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+  switch (cmd->argc - 1)
+    {
+    default:
+      CONF_ERROR (cmd, "requires a table or table plus 3 fields: "
+		  "[table] filename count dir");
+    case 1:
+      add_config_param_str ("SQLLogHits", 4, (void *) cmd->argv[1],
+			    "filename", "count", "");
+      break;
+    case 3:
+      add_config_param_str ("SQLLogHits", 4,
+			    (void *) cmd->argv[1], (void *) cmd->argv[2],
+			    (void *) cmd->argv[3], "");
+
+    case 4:
+      add_config_param_str ("SQLLogHits", 4,
+			    (void *) cmd->argv[1], (void *) cmd->argv[2],
+			    (void *) cmd->argv[3], (void *) cmd->argv[4]);
+    }
+  return HANDLED (cmd);
+}
+
+MODRET
+set_sqllogdirs (cmd_rec * cmd)
+{
+  int b;
+
+  CHECK_ARGS (cmd, 1);
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+  if ((b = get_boolean (cmd, 1)) == -1)
+    add_config_param_str ("SQLLogDirs", 1, (void *) cmd->argv[1]);
+  else if (b)
+    add_config_param_str ("SQLLogDirs", 1, "fcdir");
+  return HANDLED (cmd);
+}
+
+MODRET
+set_sqlratios (cmd_rec * cmd)
+{
+  int b;
+
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+  switch (cmd->argc - 1)
+    {
+    default:
+      CONF_ERROR (cmd, "requires a boolean or 4 field names: "
+		  "frate fcred brate bcred");
+    case 1:
+      if ((b = get_boolean (cmd, 1)) == -1)
+	CONF_ERROR (cmd, "requires a boolean or 4 field names: "
+		    "frate fcred brate bcred");
+      if (b)
+	add_config_param_str ("SQLRatios", 4,
+			      "frate", "fcred", "brate", "bcred");
+      break;
+
+    case 4:
+      add_config_param_str ("SQLRatios", 4,
+			    (void *) cmd->argv[1], (void *) cmd->argv[2],
+			    (void *) cmd->argv[3], (void *) cmd->argv[4]);
+    }
+  return HANDLED (cmd);
+}
+
+MODRET
+set_sqlempty (cmd_rec * cmd)
+{
+  int b;
+  config_rec *c;
+
+  CHECK_ARGS (cmd, 1);
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+
+  if ((b = get_boolean (cmd, 1)) == -1)
+    {
+      if (!strcasecmp (cmd->argv[1], "set"))
+	b = 2;
+      else
+	CONF_ERROR (cmd, "requires 'set' or a boolean value");
+    }
+  c = add_config_param (cmd->argv[0], 1, (void *) b);
+  c->flags |= CF_MERGEDOWN;
+  return HANDLED (cmd);
+}
+
+MODRET
+add_globalstr (cmd_rec * cmd)
+{
+  CHECK_ARGS (cmd, 1);
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+  add_config_param_str (cmd->argv[0], 1, (void *) cmd->argv[1]);
+  return HANDLED (cmd);
+}
+
+MODRET
+add_virtualstr (cmd_rec * cmd)
+{
+  CHECK_ARGS (cmd, 1);
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL | CONF_VIRTUAL);
+  add_config_param_str (cmd->argv[0], 1, (void *) cmd->argv[1]);
+  return HANDLED (cmd);
+}
+
+MODRET
+add_globalbool (cmd_rec * cmd)
+{
+  int b;
+  config_rec *c;
+
+  CHECK_ARGS (cmd, 1);
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL);
+
+  b = get_boolean (cmd, 1);
+  if (b == -1)
+    CONF_ERROR (cmd, "requires a boolean value");
+  c = add_config_param (cmd->argv[0], 1, (void *) b);
+  c->flags |= CF_MERGEDOWN;
+  return HANDLED (cmd);
+}
+
+MODRET
+add_virtualbool (cmd_rec * cmd)
+{
+  int b;
+  config_rec *c;
+
+  CHECK_ARGS (cmd, 1);
+  CHECK_CONF (cmd, CONF_ROOT | CONF_GLOBAL | CONF_VIRTUAL);
+
+  b = get_boolean (cmd, 1);
+  if (b == -1)
+    CONF_ERROR (cmd, "requires a boolean value");
+  c = add_config_param (cmd->argv[0], 1, (void *) b);
+  c->flags |= CF_MERGEDOWN;
+  return HANDLED (cmd);
+}
+
+static conftable sqlpw_conftab[] = {
+/* *INDENT-OFF* */
+
+  { "SQLLogHosts",            set_sqlloghosts,   NULL },
+  { "SQLLogStats",            set_sqllogstats,   NULL },
+  { "SQLLogHits",             set_sqlloghits,    NULL },
+  { "SQLLogDirs",             set_sqllogdirs,    NULL },
+  { "SQLRatios",              set_sqlratios,     NULL },
+  { "SQLEmptyPasswords",      set_sqlempty,      NULL },
+
+  { "SQLUserTable",           add_globalstr,     NULL },
+  { "SQLUsernameField",       add_globalstr,     NULL },
+  { "SQLUidField",            add_globalstr,     NULL },
+  { "SQLGidField",            add_globalstr,     NULL },
+  { "SQLPasswordField",       add_globalstr,     NULL },
+  { "SQLHomedirField",        add_globalstr,     NULL },
+  { "SQLLoginCountField",     add_globalstr,     NULL },
+  { "SQLHomedir",             add_globalstr,     NULL },
+
+  { "SQLKeyField",            add_globalstr,     NULL },
+  { "SQLKey",                 add_virtualstr,    NULL },
+
+  { "SQLAuthoritative",       add_virtualbool,   NULL },
+  { "SQLEncryptedPasswords",  add_globalbool,    NULL },
+  { "SQLPlaintextPasswords",  add_globalbool,    NULL },
+
+  { NULL, NULL, NULL }
+
+/* *INDENT-ON* */
+};
+
+/* **************************************************************** */
+
+static int
+sqlpw_child_init ()
+{
+  config_rec *c;
+  MODRET mr;
+
+  memset (&g, 0, sizeof (g));
+  g.ok = TRUE;
+
+  g.sql_passwd = get_param_ptr (CURRENT_CONF, "SQLPasswordField", FALSE);
+  g.sql_flogs = get_param_ptr (CURRENT_CONF, "SQLLoginCountField", FALSE);
+  g.sql_uid = get_param_ptr (CURRENT_CONF, "SQLUidField", FALSE);
+  g.sql_gid = get_param_ptr (CURRENT_CONF, "SQLGidField", FALSE);
+
+  if (!(g.homedir = get_param_ptr (CURRENT_CONF, "SQLHomedir", FALSE)))
+    {
+      g.sql_fhdir = get_param_ptr (CURRENT_CONF, "SQLHomedirField", FALSE);
+    }
+  if ((c = find_config (CURRENT_CONF, CONF_PARAM, "SQLLogHosts", FALSE)))
+    {
+      g.sql_fhost = c->argv[0];
+      g.sql_faddr = c->argv[1];
+      g.sql_ftime = c->argv[2];
+    }
+  if ((c = find_config (CURRENT_CONF, CONF_PARAM, "SQLLogStats", FALSE)))
+    {
+      g.sql_fstor = c->argv[0];
+      g.sql_fretr = c->argv[1];
+      g.sql_bstor = c->argv[2];
+      g.sql_bretr = c->argv[3];
+    }
+  if ((c = find_config (CURRENT_CONF, CONF_PARAM, "SQLRatios", FALSE)))
+    {
+      if (!g.sql_fstor)
+	log_pri (LOG_WARNING, "sqlpw: warning: SQLRatios directive "
+		 "ineffective without SQLLogStats on");
+      g.sql_frate = c->argv[0];
+      g.sql_fcred = c->argv[1];
+      g.sql_brate = c->argv[2];
+      g.sql_bcred = c->argv[3];
+    }
+  if ((c = find_config (CURRENT_CONF, CONF_PARAM, "SQLLogHits", FALSE)))
+    {
+      g.sql_hittable = c->argv[0];
+      g.sql_filename = c->argv[1];
+      g.sql_hits = c->argv[2];
+      g.sql_dir = c->argv[3];
+    }
+  g.sql_fcdir = get_param_ptr (CURRENT_CONF, "SQLLogDirs", FALSE);
+
+  g.sql_usertable = get_param_ptr (CURRENT_CONF, "SQLUserTable",
+				   FALSE) ? : "users";
+  g.sql_userid = get_param_ptr (CURRENT_CONF, "SQLUsernameField",
+				FALSE) ? : "userid";
+
+  g.sql_keyfld = get_param_ptr (CURRENT_CONF, "SQLKeyField", FALSE);
+  g.sql_key = get_param_ptr (CURRENT_CONF, "SQLKey", FALSE);
+
+  if (!(g.homedir || g.sql_fhdir) && !g.sql_fhost
+      && !g.sql_fstor && !g.sql_fcdir)
+    return 0;
+
+  mr = modsql_open (NULL);
+  if (MODRET_ISHANDLED (mr))
+    {
+      log_debug (DEBUG3, "%s: configured: %s%s%s%s%s%s%s%s",
+		 MOD_SQL_VERSION,
+		 (g.sql_passwd && (g.homedir || g.sql_fhdir))
+		 ? "auth " : "",
+		 g.homedir ? "homedir " : "",
+		 g.sql_fhdir ? "homedirfield " : "",
+		 g.sql_fhost ? "loghosts " : "",
+		 g.sql_fstor ? "logstats " : "",
+		 g.sql_frate ? "ratios " : "",
+		 g.sql_hittable ? "loghits " : "",
+		 g.sql_fcdir ? "logdirs " : "");
+    }
+  else
+    {
+      memset (&g, 0, sizeof (g));
+      log_debug (DEBUG3, "%s: unconfigured: no backend could connect",
+		 MOD_SQL_VERSION);
+    }
+  return 0;
+}
+
+static int
+sqlpw_parent_init (void)
+{
+  /* FIXME: add db init stuff once parent_init() actually works. */
+  return 0;
+}
+
+module sqlpw_module = {
+  NULL, NULL,			/* Always NULL */
+  0x20,				/* API Version 2.0 */
+  "sql",
+  sqlpw_conftab,		/* SQL configuration handler table */
+  sqlpw_cmdtab,			/* SQL command handler table */
+  sqlpw_authtab,		/* SQL authentication handler table */
+  sqlpw_parent_init,		/* Pre-fork "parent mode" init */
+  sqlpw_child_init		/* Post-fork "child mode" init */
+};
