Add a parameter to IPcheck_connect_fail() to support IAuth IP spoofing.
[ircu2.10.12-pk.git] / ircd / s_auth.c
index 15ccb1335b649a133a626301c9fcc12b8fcd9ee7..924bd50ab13dcc36df46936301348155a327312d 100644 (file)
@@ -50,7 +50,9 @@
 #include "ircd_snprintf.h"
 #include "ircd_string.h"
 #include "list.h"
+#include "msg.h"       /* for MAXPARA */
 #include "numeric.h"
+#include "numnicks.h"
 #include "querycmds.h"
 #include "random.h"
 #include "res.h"
@@ -82,6 +84,8 @@ enum AuthRequestFlag {
     AR_IAUTH_HURRY,     /**< we told iauth to hurry up */
     AR_IAUTH_USERNAME,  /**< iauth sent a username (preferred or forced) */
     AR_IAUTH_FUSERNAME, /**< iauth sent a forced username */
+    AR_IAUTH_SOFT_DONE, /**< iauth has no objection to client */
+    AR_PASSWORD_CHECKED, /**< client password already checked */
     AR_NUM_FLAGS
 };
 
@@ -95,7 +99,7 @@ struct AuthRequest {
   struct irc_sockaddr local;      /**< local endpoint address */
   struct irc_in_addr  original;   /**< original client IP address */
   struct Socket       socket;     /**< socket descriptor for auth queries */
-  struct Timer        timeout;    /**< timeout timer for auth queries */
+  struct Timer        timeout;    /**< timeout timer for ident and dns queries */
   struct AuthRequestFlags flags;  /**< current state of request */
   unsigned int        cookie;     /**< cookie the user must PONG */
   unsigned short      port;       /**< client's remote port number */
@@ -115,6 +119,7 @@ static struct {
   MSG("NOTICE AUTH :*** Checking Ident\r\n"),
   MSG("NOTICE AUTH :*** Got ident response\r\n"),
   MSG("NOTICE AUTH :*** No ident response\r\n"),
+  MSG("NOTICE AUTH :*** \r\n"),
   MSG("NOTICE AUTH :*** Your forward and reverse DNS do not match, "
     "ignoring hostname.\r\n"),
   MSG("NOTICE AUTH :*** Invalid hostname\r\n")
@@ -129,6 +134,7 @@ typedef enum {
   REPORT_DO_ID,
   REPORT_FIN_ID,
   REPORT_FAIL_ID,
+  REPORT_FAIL_IAUTH,
   REPORT_IP_MISMATCH,
   REPORT_INVAL_DNS
 } ReportType;
@@ -178,13 +184,13 @@ struct IAuth {
 };
 
 /** Return whether flag \a flag is set on \a iauth. */
-#define IAuthHas(iauth, flag) FlagHas(&iauth->i_flags, flag)
+#define IAuthHas(iauth, flag) ((iauth) && FlagHas(&(iauth)->i_flags, flag))
 /** Set flag \a flag on \a iauth. */
-#define IAuthSet(iauth, flag) FlagSet(&iauth->i_flags, flag)
+#define IAuthSet(iauth, flag) FlagSet(&(iauth)->i_flags, flag)
 /** Clear flag \a flag from \a iauth. */
-#define IAuthClr(iauth, flag) FlagClr(&iauth->i_flags, flag)
+#define IAuthClr(iauth, flag) FlagClr(&(iauth)->i_flags, flag)
 /** Get connected flag for \a iauth. */
-#define i_GetConnected(iauth) (s_fd(i_socket(iauth)) > -1)
+#define i_GetConnected(iauth) ((iauth) && s_fd(i_socket(iauth)) > -1)
 
 /** Return socket event generator for \a iauth. */
 #define i_socket(iauth) (&(iauth)->i_socket)
@@ -193,16 +199,19 @@ struct IAuth {
 /** Return outbound message queue for \a iauth. */
 #define i_sendQ(iauth) (&(iauth)->i_sendQ)
 /** Return debug level for \a iauth. */
-#define i_debug(iauth) (iauth->i_debug)
+#define i_debug(iauth) ((iauth)->i_debug)
 
 /** Active instance of IAuth. */
-struct IAuth *iauth;
+static struct IAuth *iauth;
+/** Freelist of AuthRequest structures. */
+static struct AuthRequest *auth_freelist;
 
 static void iauth_sock_callback(struct Event *ev);
 static void iauth_stderr_callback(struct Event *ev);
 static int sendto_iauth(struct Client *cptr, const char *format, ...);
 static int preregister_user(struct Client *cptr);
-typedef int (*iauth_cmd_handler)(struct IAuth *iauth, struct Client *cli, char *params);
+typedef int (*iauth_cmd_handler)(struct IAuth *iauth, struct Client *cli,
+                                int parc, char **params);
 
 /** Set username for user associated with \a auth.
  * @param[in] auth Client authorization request to work on.
@@ -322,7 +331,7 @@ static int auth_set_username(struct AuthRequest *auth)
     if (!lower && !upper)
       goto badid;
     /* Final character must not be punctuation. */
-    if (!IsAlnum(ch))
+    if (!IsAlnum(last))
       goto badid;
   }
 
@@ -352,11 +361,9 @@ badid:
  * destroy \a auth, clear the password, set the username, and register
  * the client.
  * @param[in] auth Authorization request to check.
- * @param[in] send_reports Passed to destroy_auth_request() if \a auth
- *   is complete.
  * @return Zero if client is kept, CPTR_KILLED if client rejected.
  */
-static int check_auth_finished(struct AuthRequest *auth, int send_reports)
+static int check_auth_finished(struct AuthRequest *auth)
 {
   enum AuthRequestFlag flag;
   int res;
@@ -370,52 +377,69 @@ static int check_auth_finished(struct AuthRequest *auth, int send_reports)
       return 0;
     }
 
+  /* If appropriate, do preliminary assignment to connection class. */
+  if (IsUserPort(auth->client)
+      && !FlagHas(&auth->flags, AR_IAUTH_HURRY)
+      && preregister_user(auth->client))
+    return CPTR_KILLED;
+
+  /* If we have not done so, check client password.  Do this as soon
+   * as possible so that iauth's challenge/response (which uses PASS
+   * for responses) is not confused with the client's password.
+   */
+  if (IsUserPort(auth->client)
+      && !FlagHas(&auth->flags, AR_PASSWORD_CHECKED))
+  {
+    struct ConfItem *aconf;
+
+    aconf = cli_confs(auth->client)->value.aconf;
+    if (aconf
+        && !EmptyString(aconf->passwd)
+        && strcmp(cli_passwd(auth->client), aconf->passwd))
+    {
+      ServerStats->is_ref++;
+      send_reply(auth->client, ERR_PASSWDMISMATCH);
+      return exit_client(auth->client, auth->client, &me, "Bad Password");
+    }
+    FlagSet(&auth->flags, AR_PASSWORD_CHECKED);
+  }
+
   /* Check if iauth is done. */
   if (FlagHas(&auth->flags, AR_IAUTH_PENDING))
   {
     /* Switch auth request to hurry-up state. */
     if (!FlagHas(&auth->flags, AR_IAUTH_HURRY))
     {
-      struct ConfItem* aconf;
-
       /* Set "hurry" flag in auth request. */
       FlagSet(&auth->flags, AR_IAUTH_HURRY);
 
-      /* Do preliminary assignment to connection class. */
-      if (preregister_user(auth->client))
-        return CPTR_KILLED;
-
-      /* Check password now (to avoid challenge/response conflicts). */
-      aconf = cli_confs(auth->client)->value.aconf;
-      if (!EmptyString(aconf->passwd)
-          && strcmp(cli_passwd(auth->client), aconf->passwd))
-      {
-        ServerStats->is_ref++;
-        send_reply(auth->client, ERR_PASSWDMISMATCH);
-        return exit_client(auth->client, auth->client, &me, "Bad Password");
-      }
-
       /* If iauth wants it, send notification. */
       if (IAuthHas(iauth, IAUTH_UNDERNET))
-        sendto_iauth(auth->client, "H %s", ConfClass(aconf));
+        sendto_iauth(auth->client, "H %s", get_client_class(auth->client));
 
       /* If iauth wants it, give client more time. */
       if (IAuthHas(iauth, IAUTH_EXTRAWAIT))
-        timer_chg(&auth->timeout, TT_RELATIVE, feature_int(FEAT_AUTH_TIMEOUT));
+        cli_firsttime(auth->client) = CurrentTime;
     }
 
     Debug((DEBUG_INFO, "Auth %p [%d] still has flag %d", auth,
            cli_fd(auth->client), AR_IAUTH_PENDING));
     return 0;
   }
+  else
+    FlagSet(&auth->flags, AR_IAUTH_HURRY);
 
-  destroy_auth_request(auth, send_reports);
-  if (!IsUserPort(auth->client))
-    return 0;
-  memset(cli_passwd(auth->client), 0, sizeof(cli_passwd(auth->client)));
-  res = auth_set_username(auth);
-  if (res == 0)
+  if (IsUserPort(auth->client))
+  {
+    memset(cli_passwd(auth->client), 0, sizeof(cli_passwd(auth->client)));
+    res = auth_set_username(auth);
+    if (res == 0)
       res = register_user(auth->client, auth->client);
+  }
+  else
+    res = 0;
+  if (res == 0)
+    destroy_auth_request(auth);
   return res;
 }
 
@@ -439,6 +463,26 @@ auth_verify_hostname(const char *host, int maxlen)
   return 1; /* it's a valid hostname */
 }
 
+/** Check whether a client already has a CONF_CLIENT configuration
+ * item.
+ *
+ * @return A pointer to the client's first CONF_CLIENT, or NULL if
+ *   there are none.
+ */
+static struct ConfItem *find_conf_client(struct Client *cptr)
+{
+  struct SLink *list;
+
+  for (list = cli_confs(cptr); list != NULL; list = list->next) {
+    struct ConfItem *aconf;
+    aconf = list->value.aconf;
+    if (aconf->status & CONF_CLIENT)
+      return aconf;
+  }
+
+  return NULL;
+}
+
 /** Assign a client to a connection class.
  * @param[in] cptr Client to assign to a class.
  * @return Zero if client is kept, CPTR_KILLED if rejected.
@@ -451,6 +495,10 @@ static int preregister_user(struct Client *cptr)
   ircd_strncpy(cli_user(cptr)->host, cli_sockhost(cptr), HOSTLEN);
   ircd_strncpy(cli_user(cptr)->realhost, cli_sockhost(cptr), HOSTLEN);
 
+  if (find_conf_client(cptr)) {
+    return 0;
+  }
+
   switch (conf_check_client(cptr))
   {
   case ACR_OK:
@@ -481,7 +529,7 @@ static int preregister_user(struct Client *cptr)
     /* Can this ever happen? */
   case ACR_BAD_SOCKET:
     ++ServerStats->is_ref;
-    IPcheck_connect_fail(cptr);
+    IPcheck_connect_fail(cptr, 0);
     return exit_client(cptr, cptr, &me, "Unknown error -- Try again");
   }
   return 0;
@@ -512,7 +560,7 @@ static void send_auth_query(struct AuthRequest* auth)
     if (IsUserPort(auth->client))
       sendheader(auth->client, REPORT_FAIL_ID);
     FlagClr(&auth->flags, AR_AUTH_PENDING);
-    check_auth_finished(auth, 0);
+    check_auth_finished(auth);
   }
 }
 
@@ -644,7 +692,7 @@ static void read_auth_reply(struct AuthRequest* auth)
   }
 
   FlagClr(&auth->flags, AR_AUTH_PENDING);
-  check_auth_finished(auth, 0);
+  check_auth_finished(auth);
 }
 
 /** Handle socket I/O activity.
@@ -686,21 +734,13 @@ static void auth_sock_callback(struct Event* ev)
 
 /** Stop an auth request completely.
  * @param[in] auth The struct AuthRequest to cancel.
- * @param[in] send_reports If non-zero, report the failure to the user.
  */
-void destroy_auth_request(struct AuthRequest* auth, int send_reports)
+void destroy_auth_request(struct AuthRequest* auth)
 {
   Debug((DEBUG_INFO, "Deleting auth request for %p", auth->client));
 
-  if (FlagHas(&auth->flags, AR_AUTH_PENDING)) {
-    if (send_reports && IsUserPort(auth->client))
-      sendheader(auth->client, REPORT_FAIL_ID);
-  }
-
   if (FlagHas(&auth->flags, AR_DNS_PENDING)) {
     delete_resolver_queries(auth);
-    if (send_reports && IsUserPort(auth->client))
-      sendheader(auth->client, REPORT_FAIL_DNS);
   }
 
   if (-1 < s_fd(&auth->socket)) {
@@ -709,8 +749,61 @@ void destroy_auth_request(struct AuthRequest* auth, int send_reports)
     s_fd(&auth->socket) = -1;
   }
 
-  timer_del(&auth->timeout);
+  if (t_active(&auth->timeout))
+    timer_del(&auth->timeout);
+
   cli_auth(auth->client) = NULL;
+  auth->next = auth_freelist;
+  auth_freelist = auth;
+}
+
+/** Handle a 'ping' (authorization) timeout for a client.
+ * @param[in] cptr The client whose session authorization has timed out.
+ * @return Zero if client is kept, CPTR_KILLED if client rejected.
+ */
+int auth_ping_timeout(struct Client *cptr)
+{
+  struct AuthRequest *auth;
+  enum AuthRequestFlag flag;
+
+  auth = cli_auth(cptr);
+
+  /* Check whether the auth request is gone (more likely, it never
+   * existed, as in an outbound server connection). */
+  if (!auth)
+      return exit_client_msg(cptr, cptr, &me, "Registration Timeout");
+
+  /* Check for a user-controlled timeout. */
+  for (flag = 0; flag <= AR_LAST_SCAN; ++flag) {
+    if (FlagHas(&auth->flags, flag)) {
+      /* Display message if they have sent a NICK and a USER but no
+       * nospoof PONG.
+       */
+      if (*(cli_name(cptr)) && cli_user(cptr) && *(cli_user(cptr))->username) {
+        send_reply(cptr, SND_EXPLICIT | ERR_BADPING,
+                   ":Your client may not be compatible with this server.");
+        send_reply(cptr, SND_EXPLICIT | ERR_BADPING,
+                   ":Compatible clients are available at %s",
+                   feature_str(FEAT_URL_CLIENTS));
+      }
+      return exit_client_msg(cptr, cptr, &me, "Registration Timeout");
+    }
+  }
+
+  /* Check for iauth timeout. */
+  if (FlagHas(&auth->flags, AR_IAUTH_PENDING)) {
+    if (IAuthHas(iauth, IAUTH_REQUIRED)
+        && !FlagHas(&auth->flags, AR_IAUTH_SOFT_DONE)) {
+      sendheader(cptr, REPORT_FAIL_IAUTH);
+      return exit_client_msg(cptr, cptr, &me, "Authorization Timeout");
+    }
+    sendto_iauth(cptr, "T");
+    FlagClr(&auth->flags, AR_IAUTH_PENDING);
+    return check_auth_finished(auth);
+  }
+
+  assert(0 && "Unexpectedly reached end of auth_ping_timeout()");
+  return 0;
 }
 
 /** Timeout a given auth request.
@@ -730,18 +823,24 @@ static void auth_timeout_callback(struct Event* ev)
     /* Report the timeout in the log. */
     log_write(LS_RESOLVER, L_INFO, 0, "Registration timeout %s",
               get_client_name(auth->client, HIDE_IP));
-    /* Tell iauth if we will let the client on. */
-    if (FlagHas(&auth->flags, AR_IAUTH_PENDING)
-        && !IAuthHas(iauth, IAUTH_REQUIRED))
-    {
-      sendto_iauth(auth->client, "T");
-      FlagClr(&auth->flags , AR_IAUTH_PENDING);
+
+    /* Notify client if ident lookup failed. */
+    if (FlagHas(&auth->flags, AR_AUTH_PENDING)) {
+      FlagClr(&auth->flags, AR_AUTH_PENDING);
+      if (IsUserPort(auth->client))
+        sendheader(auth->client, REPORT_FAIL_ID);
     }
+
+    /* Likewise if dns lookup failed. */
+    if (FlagHas(&auth->flags, AR_DNS_PENDING)) {
+      FlagClr(&auth->flags, AR_DNS_PENDING);
+      delete_resolver_queries(auth);
+      if (IsUserPort(auth->client))
+        sendheader(auth->client, REPORT_FAIL_DNS);
+    }
+
     /* Try to register the client. */
-    check_auth_finished(auth, 1);
-    /* If that failed, kick them off. */
-    if (!IsUser(auth->client))
-      exit_client(auth->client, auth->client, &me, "Authorization timed out");
+    check_auth_finished(auth);
   }
 }
 
@@ -763,21 +862,18 @@ static void auth_dns_callback(void* vptr, const struct irc_in_addr *addr, const
     if (IsUserPort(auth->client))
       sendheader(auth->client, REPORT_FAIL_DNS);
     sendto_iauth(auth->client, "d");
-  } else if (irc_in_addr_cmp(addr, &cli_ip(auth->client))
-             && irc_in_addr_cmp(addr, &auth->original)) {
+  } else if (!irc_in_addr_valid(addr)
+             || (irc_in_addr_cmp(&cli_ip(auth->client), addr)
+                 && irc_in_addr_cmp(&auth->original, addr))) {
     /* IP for hostname did not match client's IP. */
     sendto_opmask_butone(0, SNO_IPMISMATCH, "IP# Mismatch: %s != %s[%s]",
                          cli_sock_ip(auth->client), h_name,
                          ircd_ntoa(addr));
     if (IsUserPort(auth->client))
       sendheader(auth->client, REPORT_IP_MISMATCH);
-    /* Clear DNS pending flag so free_client doesn't ask the resolver
-     * to delete the query that just finished.
-     */
     if (feature_bool(FEAT_KILL_IPMISMATCH)) {
-      IPcheck_disconnect(auth->client);
-      Count_unknowndisconnects(UserStats);
-      free_client(auth->client);
+      exit_client(auth->client, auth->client, &me, "IP mismatch");
+      return;
     }
   } else if (!auth_verify_hostname(h_name, HOSTLEN)) {
     /* Hostname did not look valid. */
@@ -791,7 +887,7 @@ static void auth_dns_callback(void* vptr, const struct irc_in_addr *addr, const
     ircd_strncpy(cli_sockhost(auth->client), h_name, HOSTLEN);
     sendto_iauth(auth->client, "N %s", h_name);
   }
-  check_auth_finished(auth, 0);
+  check_auth_finished(auth);
 }
 
 /** Flag the client to show an attempt to contact the ident server on
@@ -821,7 +917,7 @@ static void start_auth_query(struct AuthRequest* auth)
   local_addr.port = 0;
   memcpy(&remote_addr.addr, &cli_ip(auth->client), sizeof(remote_addr.addr));
   remote_addr.port = 113;
-  fd = os_socket(&local_addr, SOCK_STREAM, "auth query");
+  fd = os_socket(&local_addr, SOCK_STREAM, "auth query", 0);
   if (fd < 0) {
     ++ServerStats->is_abad;
     if (IsUserPort(auth->client))
@@ -899,12 +995,16 @@ void start_auth(struct Client* client)
   if (cli_fd(client) > HighestFd)
     HighestFd = cli_fd(client);
   LocalClientArray[cli_fd(client)] = client;
-  add_client_to_list(client);
   socket_events(&(cli_socket(client)), SOCK_ACTION_SET | SOCK_EVENT_READABLE);
 
   /* Allocate the AuthRequest. */
-  auth = MyCalloc(1, sizeof(*auth));
+  auth = auth_freelist;
+  if (auth)
+      auth_freelist = auth->next;
+  else
+      auth = MyMalloc(sizeof(*auth));
   assert(0 != auth);
+  memset(auth, 0, sizeof(*auth));
   auth->client = client;
   cli_auth(client) = auth;
   s_fd(&auth->socket) = -1;
@@ -917,19 +1017,11 @@ void start_auth(struct Client* client)
     ++ServerStats->is_abad;
     if (IsUserPort(auth->client))
       sendheader(auth->client, REPORT_FAIL_ID);
-    IPcheck_disconnect(auth->client);
-    Count_unknowndisconnects(UserStats);
-    free_client(auth->client);
+    exit_client(auth->client, auth->client, &me, "Socket local/peer lookup failed");
     return;
   }
   auth->port = remote.port;
 
-  /* Try to start DNS lookup. */
-  start_dns_query(auth);
-
-  /* Try to start ident lookup. */
-  start_auth_query(auth);
-
   /* Set required client inputs for users. */
   if (IsUserPort(client)) {
     cli_user(client) = make_user(client);
@@ -941,8 +1033,17 @@ void start_auth(struct Client* client)
     start_iauth_query(auth);
   }
 
+  /* Try to start DNS lookup. */
+  start_dns_query(auth);
+
+  /* Try to start ident lookup. */
+  start_auth_query(auth);
+
+  /* Add client to GlobalClientList. */
+  add_client_to_list(client);
+
   /* Check which auth events remain pending. */
-  check_auth_finished(auth, 0);
+  check_auth_finished(auth);
 }
 
 /** Mark that a user has PONGed while unregistered.
@@ -961,17 +1062,22 @@ int auth_set_pong(struct AuthRequest *auth, unsigned int cookie)
                ":To connect, type /QUOTE PONG %u", auth->cookie);
     return 0;
   }
+  cli_lasttime(auth->client) = CurrentTime;
   FlagClr(&auth->flags, AR_NEEDS_PONG);
-  return check_auth_finished(auth, 0);
+  return check_auth_finished(auth);
 }
 
 /** Record a user's claimed username and userinfo.
  * @param[in] auth Authorization request for client.
  * @param[in] username Client's asserted username.
+ * @param[in] hostname Third argument of USER command (client's
+ *   hostname, per RFC 1459).
+ * @param[in] servername Fourth argument of USER command (server's
+ *   name, per RFC 1459).
  * @param[in] userinfo Client's asserted self-description.
  * @return Zero if client should be kept, CPTR_KILLED if rejected.
  */
-int auth_set_user(struct AuthRequest *auth, const char *username, const char *userinfo)
+int auth_set_user(struct AuthRequest *auth, const char *username, const char *hostname, const char *servername, const char *userinfo)
 {
   struct Client *cptr;
 
@@ -984,10 +1090,10 @@ int auth_set_user(struct AuthRequest *auth, const char *username, const char *us
   ircd_strncpy(cli_user(cptr)->username, username, USERLEN);
   ircd_strncpy(cli_user(cptr)->host, cli_sockhost(cptr), HOSTLEN);
   if (IAuthHas(iauth, IAUTH_UNDERNET))
-    sendto_iauth(cptr, "U %s %s", username, userinfo);
+    sendto_iauth(cptr, "U %s %s %s :%s", username, hostname, servername, userinfo);
   else if (IAuthHas(iauth, IAUTH_ADDLINFO))
     sendto_iauth(cptr, "U %s", username);
-  return check_auth_finished(auth, 0);
+  return check_auth_finished(auth);
 }
 
 /** Handle authorization-related aspects of initial nickname selection.
@@ -1013,7 +1119,7 @@ int auth_set_nick(struct AuthRequest *auth, const char *nickname)
   }
   if (IAuthHas(iauth, IAUTH_UNDERNET))
     sendto_iauth(auth->client, "n %s", nickname);
-  return check_auth_finished(auth, 0);
+  return check_auth_finished(auth);
 }
 
 /** Record a user's password.
@@ -1025,7 +1131,7 @@ int auth_set_password(struct AuthRequest *auth, const char *password)
 {
   assert(auth != NULL);
   if (IAuthHas(iauth, IAUTH_ADDLINFO))
-    sendto_iauth(auth->client, "P %s", password);
+    sendto_iauth(auth->client, "P :%s", password);
   return 0;
 }
 
@@ -1037,6 +1143,17 @@ void auth_send_exit(struct Client *cptr)
   sendto_iauth(cptr, "D");
 }
 
+/** Forward an XREPLY on to iauth.
+ * @param[in] sptr Source of the XREPLY.
+ * @param[in] routing Routing information for the original XQUERY.
+ * @param[in] reply Contents of the reply.
+ */
+void auth_send_xreply(struct Client *sptr, const char *routing,
+                     const char *reply)
+{
+  sendto_iauth(NULL, "X %#C %s :%s", sptr, routing, reply);
+}
+
 /** Mark that a user has started capabilities negotiation.
  * This blocks authorization until auth_cap_done() is called.
  * @param[in] auth Authorization request for client.
@@ -1058,7 +1175,7 @@ int auth_cap_done(struct AuthRequest *auth)
 {
   assert(auth != NULL);
   FlagClr(&auth->flags, AR_CAP_PENDING);
-  return check_auth_finished(auth, 0);
+  return check_auth_finished(auth);
 }
 
 /** Attempt to spawn the process for an IAuth instance.
@@ -1085,13 +1202,17 @@ int iauth_do_spawn(struct IAuth *iauth, int automatic)
 
   /* Attempt to allocate a pair of sockets. */
   res = os_socketpair(s_io);
-  if (res)
-    return errno;
+  if (res) {
+    res = errno;
+    Debug((DEBUG_INFO, "Unable to create IAuth socketpair: %s", strerror(res)));
+    return res;
+  }
 
   /* Mark the parent's side of the pair (element 0) as non-blocking. */
   res = os_set_nonblocking(s_io[0]);
   if (!res) {
     res = errno;
+    Debug((DEBUG_INFO, "Unable to make IAuth socket non-blocking: %s", strerror(res)));
     close(s_io[1]);
     close(s_io[0]);
     return res;
@@ -1102,6 +1223,7 @@ int iauth_do_spawn(struct IAuth *iauth, int automatic)
                    SS_CONNECTED, SOCK_EVENT_READABLE, s_io[0]);
   if (!res) {
     res = errno;
+    Debug((DEBUG_INFO, "Unable to register IAuth socket: %s", strerror(res)));
     close(s_io[1]);
     close(s_io[0]);
     return res;
@@ -1111,6 +1233,7 @@ int iauth_do_spawn(struct IAuth *iauth, int automatic)
   res = os_socketpair(s_err);
   if (res) {
     res = errno;
+    Debug((DEBUG_INFO, "Unable to create IAuth stderr: %s", strerror(res)));
     socket_del(i_socket(iauth));
     close(s_io[1]);
     close(s_io[0]);
@@ -1121,6 +1244,7 @@ int iauth_do_spawn(struct IAuth *iauth, int automatic)
   res = os_set_nonblocking(s_err[0]);
   if (!res) {
     res = errno;
+    Debug((DEBUG_INFO, "Unable to make IAuth stderr non-blocking: %s", strerror(res)));
     close(s_err[1]);
     close(s_err[0]);
     socket_del(i_socket(iauth));
@@ -1134,6 +1258,7 @@ int iauth_do_spawn(struct IAuth *iauth, int automatic)
                    SS_CONNECTED, SOCK_EVENT_READABLE, s_err[0]);
   if (!res) {
     res = errno;
+    Debug((DEBUG_INFO, "Unable to register IAuth stderr: %s", strerror(res)));
     close(s_err[1]);
     close(s_err[0]);
     socket_del(i_socket(iauth));
@@ -1147,6 +1272,7 @@ int iauth_do_spawn(struct IAuth *iauth, int automatic)
   if (cpid < 0) {
     /* Error forking the child, still in parent. */
     res = errno;
+    Debug((DEBUG_INFO, "Unable to fork IAuth child: %s", strerror(res)));
     socket_del(i_stderr(iauth));
     close(s_err[1]);
     close(s_err[0]);
@@ -1206,17 +1332,15 @@ int auth_spawn(int argc, char *argv[])
         same = 0;
     }
     /* Check that we have no more pre-existing arguments. */
-    if (iauth->i_argv[ii])
+    if (same && iauth->i_argv[ii])
       same = 0;
-    /* If they are the same and still connected, clear the "closing" flag and exit.*/
+    /* If they are the same and still connected, clear the "closing" flag and exit. */
     if (same && i_GetConnected(iauth)) {
+      Debug((DEBUG_INFO, "Reusing existing IAuth process"));
       IAuthClr(iauth, IAUTH_CLOSING);
       return 2;
     }
-    /* Deallocate old argv elements. */
-    for (ii = 0; iauth->i_argv[ii]; ++ii)
-      MyFree(iauth->i_argv[ii]);
-    MyFree(iauth->i_argv);
+    auth_close_unused();
   }
 
   /* Need to initialize a new connection. */
@@ -1246,24 +1370,28 @@ void auth_mark_closing(void)
  */
 static void iauth_disconnect(struct IAuth *iauth)
 {
-  if (!i_GetConnected(iauth))
+  if (iauth == NULL)
     return;
 
   /* Close main socket. */
-  close(s_fd(i_socket(iauth)));
-  socket_del(i_socket(iauth));
-  s_fd(i_socket(iauth)) = -1;
+  if (s_fd(i_socket(iauth)) != -1) {
+    close(s_fd(i_socket(iauth)));
+    socket_del(i_socket(iauth));
+    s_fd(i_socket(iauth)) = -1;
+  }
 
   /* Close error socket. */
-  close(s_fd(i_stderr(iauth)));
-  socket_del(i_stderr(iauth));
-  s_fd(i_stderr(iauth)) = -1;
+  if (s_fd(i_stderr(iauth)) != -1) {
+    close(s_fd(i_stderr(iauth)));
+    socket_del(i_stderr(iauth));
+    s_fd(i_stderr(iauth)) = -1;
+  }
 }
 
 /** Close all %IAuth connections marked as closing. */
 void auth_close_unused(void)
 {
-  if (iauth && IAuthHas(iauth, IAUTH_CLOSING)) {
+  if (IAuthHas(iauth, IAUTH_CLOSING)) {
     int ii;
     iauth_disconnect(iauth);
     if (iauth->i_argv) {
@@ -1338,6 +1466,7 @@ static int sendto_iauth(struct Client *cptr, const char *format, ...)
   /* Tack it onto the iauth sendq and try to write it. */
   ++iauth->i_sendM;
   msgq_add(i_sendQ(iauth), mb, 0);
+  msgq_clean(mb);
   iauth_write(iauth);
   return 1;
 }
@@ -1345,26 +1474,30 @@ static int sendto_iauth(struct Client *cptr, const char *format, ...)
 /** Send text to interested operators (SNO_AUTH server notice).
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Text to send.
  * @return Zero.
  */
-static int iauth_cmd_snotice(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_snotice(struct IAuth *iauth, struct Client *cli,
+                            int parc, char **params)
 {
-  sendto_opmask_butone(NULL, SNO_AUTH, "%s", params);
+  sendto_opmask_butone(NULL, SNO_AUTH, "%s", params[0]);
   return 0;
 }
 
 /** Set the debug level for the session.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params String starting with an integer.
  * @return Zero.
  */
-static int iauth_cmd_debuglevel(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_debuglevel(struct IAuth *iauth, struct Client *cli,
+                               int parc, char **params)
 {
   int new_level;
 
-  new_level = atoi(params);
+  new_level = parc > 0 ? atoi(params[0]) : 0;
   if (i_debug(iauth) > 0 || new_level > 0) {
     /* The "ia_dbg" name is borrowed from (IRCnet) ircd. */
     sendto_opmask_butone(NULL, SNO_AUTH, "ia_dbg = %d", new_level);
@@ -1384,10 +1517,12 @@ static int iauth_cmd_debuglevel(struct IAuth *iauth, struct Client *cli, char *p
  *
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Zero or more policy options.
  * @return Zero.
  */
-static int iauth_cmd_policy(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_policy(struct IAuth *iauth, struct Client *cli,
+                           int parc, char **params)
 {
   enum IAuthFlag flag;
   char *p;
@@ -1396,44 +1531,85 @@ static int iauth_cmd_policy(struct IAuth *iauth, struct Client *cli, char *param
   for (flag = IAUTH_FIRST_OPTION; flag < IAUTH_LAST_FLAG; ++flag)
     IAuthClr(iauth, flag);
 
-  /* Parse new policy set. */
-  for (p = params; *p; p++) switch (*p) {
-  case 'A': IAuthSet(iauth, IAUTH_ADDLINFO); break;
-  case 'R': IAuthSet(iauth, IAUTH_REQUIRED); break;
-  case 'T': IAuthSet(iauth, IAUTH_TIMEOUT); break;
-  case 'W': IAuthSet(iauth, IAUTH_EXTRAWAIT); break;
-  case 'U': IAuthSet(iauth, IAUTH_UNDERNET); break;
-  }
+  if (parc > 0) /* only try to parse if we were given a policy string */
+    /* Parse new policy set. */
+    for (p = params[0]; *p; p++) switch (*p) {
+    case 'A': IAuthSet(iauth, IAUTH_ADDLINFO); break;
+    case 'R': IAuthSet(iauth, IAUTH_REQUIRED); break;
+    case 'T': IAuthSet(iauth, IAUTH_TIMEOUT); break;
+    case 'W': IAuthSet(iauth, IAUTH_EXTRAWAIT); break;
+    case 'U': IAuthSet(iauth, IAUTH_UNDERNET); break;
+    }
 
   /* Optionally notify operators. */
   if (i_debug(iauth) > 0)
-    sendto_opmask_butone(NULL, SNO_AUTH, "iauth options: %s", params);
+    sendto_opmask_butone(NULL, SNO_AUTH, "iauth options: %s", params[0]);
   return 0;
 }
 
 /** Set the iauth program version number.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Version number or name.
  * @return Zero.
  */
-static int iauth_cmd_version(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_version(struct IAuth *iauth, struct Client *cli,
+                            int parc, char **params)
 {
   MyFree(iauth->i_version);
-  while (IsSpace(*params))
-    ++params;
-  DupString(iauth->i_version, params);
-  sendto_opmask_butone(NULL, SNO_AUTH, "iauth version %s running.", iauth->i_version);
+  DupString(iauth->i_version, parc > 0 ? params[0] : "<NONE>");
+  sendto_opmask_butone(NULL, SNO_AUTH, "iauth version %s running.",
+                      iauth->i_version);
   return 0;
 }
 
+/** Paste a parameter list together into a single string.
+ * @param[in] parc Number of parameters.
+ * @param[in] params Parameter list to paste together.
+ * @return Pasted parameter list.
+ */
+static char *paste_params(int parc, char **params)
+{
+  char *str, *tmp;
+  int len = 0, lengths[MAXPARA], i;
+
+  /* Compute the length... */
+  for (i = 0; i < parc; i++)
+    len += lengths[i] = strlen(params[i]);
+
+  /* Allocate memory, accounting for string lengths, spaces (parc - 1), a
+   * sentinel, and the trailing \0
+   */
+  str = MyMalloc(len + parc + 1);
+
+  /* Build the pasted string */
+  for (tmp = str, i = 0; i < parc; i++) {
+    if (i) /* add space separator... */
+      *(tmp++) = ' ';
+    if (i == parc - 1) /* add colon sentinel */
+      *(tmp++) = ':';
+
+    /* Copy string component... */
+    memcpy(tmp, params[i], lengths[i]);
+    tmp += lengths[i]; /* move to end of string */
+  }
+
+  /* terminate the string... */
+  *tmp = '\0';
+
+  return str; /* return the pasted string */
+}
+
 /** Clear cached iauth configuration information.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (0).
  * @param[in] params Parameter list (ignored).
  * @return Zero.
  */
-static int iauth_cmd_newconfig(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_newconfig(struct IAuth *iauth, struct Client *cli,
+                              int parc, char **params)
 {
   struct SLink *head;
   struct SLink *next;
@@ -1452,10 +1628,12 @@ static int iauth_cmd_newconfig(struct IAuth *iauth, struct Client *cli, char *pa
 /** Append iauth configuration information.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters.
  * @param[in] params Description of configuration element.
  * @return Zero.
  */
-static int iauth_cmd_config(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_config(struct IAuth *iauth, struct Client *cli,
+                           int parc, char **params)
 {
   struct SLink *node;
 
@@ -1465,19 +1643,20 @@ static int iauth_cmd_config(struct IAuth *iauth, struct Client *cli, char *param
   } else {
     node = iauth->i_config = make_link();
   }
-  while (IsSpace(*params))
-    ++params;
-  DupString(node->value.cp, params);
+  node->value.cp = paste_params(parc, params);
+  node->next = 0; /* must be explicitly cleared */
   return 0;
 }
 
 /** Clear cached iauth configuration information.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (0).
  * @param[in] params Parameter list (ignored).
  * @return Zero.
  */
-static int iauth_cmd_newstats(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_newstats(struct IAuth *iauth, struct Client *cli,
+                             int parc, char **params)
 {
   struct SLink *head;
   struct SLink *next;
@@ -1496,10 +1675,12 @@ static int iauth_cmd_newstats(struct IAuth *iauth, struct Client *cli, char *par
 /** Append iauth statistics information.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters.
  * @param[in] params Statistics element.
  * @return Zero.
  */
-static int iauth_cmd_stats(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_stats(struct IAuth *iauth, struct Client *cli,
+                          int parc, char **params)
 {
   struct SLink *node;
   if (iauth->i_stats) {
@@ -1508,24 +1689,25 @@ static int iauth_cmd_stats(struct IAuth *iauth, struct Client *cli, char *params
   } else {
     node = iauth->i_stats = make_link();
   }
-  while (IsSpace(*params))
-    ++params;
-  DupString(node->value.cp, params);
+  node->value.cp = paste_params(parc, params);
+  node->next = 0; /* must be explicitly cleared */
   return 0;
 }
 
 /** Set client's username to a trusted string even if it breaks the rules.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Forced username.
  * @return One.
  */
-static int iauth_cmd_username_forced(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_username_forced(struct IAuth *iauth, struct Client *cli,
+                                    int parc, char **params)
 {
   assert(cli_auth(cli) != NULL);
   FlagClr(&cli_auth(cli)->flags, AR_AUTH_PENDING);
-  if (!EmptyString(params)) {
-    ircd_strncpy(cli_username(cli), params, USERLEN);
+  if (!EmptyString(params[0])) {
+    ircd_strncpy(cli_username(cli), params[0], USERLEN);
     SetGotId(cli);
     FlagSet(&cli_auth(cli)->flags, AR_IAUTH_USERNAME);
     FlagSet(&cli_auth(cli)->flags, AR_IAUTH_FUSERNAME);
@@ -1536,15 +1718,17 @@ static int iauth_cmd_username_forced(struct IAuth *iauth, struct Client *cli, ch
 /** Set client's username to a trusted string.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Trusted username.
  * @return One.
  */
-static int iauth_cmd_username_good(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_username_good(struct IAuth *iauth, struct Client *cli,
+                                  int parc, char **params)
 {
   assert(cli_auth(cli) != NULL);
   FlagClr(&cli_auth(cli)->flags, AR_AUTH_PENDING);
-  if (!EmptyString(params)) {
-    ircd_strncpy(cli_username(cli), params, USERLEN);
+  if (!EmptyString(params[0])) {
+    ircd_strncpy(cli_username(cli), params[0], USERLEN);
     SetGotId(cli);
     FlagSet(&cli_auth(cli)->flags, AR_IAUTH_USERNAME);
   }
@@ -1554,29 +1738,33 @@ static int iauth_cmd_username_good(struct IAuth *iauth, struct Client *cli, char
 /** Set client's username to an untrusted string.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Untrusted username.
  * @return One.
  */
-static int iauth_cmd_username_bad(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_username_bad(struct IAuth *iauth, struct Client *cli,
+                                 int parc, char **params)
 {
   assert(cli_auth(cli) != NULL);
   FlagClr(&cli_auth(cli)->flags, AR_AUTH_PENDING);
-  if (!EmptyString(params))
-    ircd_strncpy(cli_user(cli)->username, params, USERLEN);
+  if (!EmptyString(params[0]))
+    ircd_strncpy(cli_user(cli)->username, params[0], USERLEN);
   return 1;
 }
 
 /** Set client's hostname.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params New hostname for client.
  * @return Non-zero if \a cli authorization should be checked for completion.
  */
-static int iauth_cmd_hostname(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_hostname(struct IAuth *iauth, struct Client *cli,
+                             int parc, char **params)
 {
   struct AuthRequest *auth;
 
-  if (EmptyString(params)) {
+  if (EmptyString(params[0])) {
     sendto_iauth(cli, "E Missing :Missing hostname parameter");
     return 0;
   }
@@ -1592,29 +1780,44 @@ static int iauth_cmd_hostname(struct IAuth *iauth, struct Client *cli, char *par
       sendheader(cli, REPORT_FIN_DNS);
   }
   /* Set hostname from params. */
-  ircd_strncpy(cli_sockhost(cli), params, HOSTLEN);
+  ircd_strncpy(cli_sockhost(cli), params[0], HOSTLEN);
+  /* If we have gotten here, the user is in a "hurry" state and has
+   * been pre-registered.  Their hostname was set during that, and
+   * needs to be overwritten now.
+   */
+  if (FlagHas(&auth->flags, AR_IAUTH_HURRY)) {
+    ircd_strncpy(cli_user(cli)->host, cli_sockhost(cli), HOSTLEN);
+    ircd_strncpy(cli_user(cli)->realhost, cli_sockhost(cli), HOSTLEN);
+  }
   return 1;
 }
 
 /** Set client's IP address.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params New IP address for client in dotted quad or
  *   standard IPv6 format.
  * @return Zero.
  */
-static int iauth_cmd_ip_address(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_ip_address(struct IAuth *iauth, struct Client *cli,
+                               int parc, char **params)
 {
   struct irc_in_addr addr;
   struct AuthRequest *auth;
 
+  if (EmptyString(params[0])) {
+    sendto_iauth(cli, "E Missing :Missing IP address parameter");
+    return 0;
+  }
+
   /* Get AuthRequest for client. */
   auth = cli_auth(cli);
   assert(auth != NULL);
 
   /* Parse the client's new IP address. */
-  if (!ircd_aton(&addr, params)) {
-    sendto_iauth(cli, "E Invalid :Unable to parse IP address [%s]", params);
+  if (!ircd_aton(&addr, params[0])) {
+    sendto_iauth(cli, "E Invalid :Unable to parse IP address [%s]", params[0]);
     return 0;
   }
 
@@ -1625,7 +1828,7 @@ static int iauth_cmd_ip_address(struct IAuth *iauth, struct Client *cli, char *p
     memcpy(&auth->original, &cli_ip(cli), sizeof(auth->original));
 
   /* Undo original IP connection in IPcheck. */
-  IPcheck_connect_fail(cli);
+  IPcheck_connect_fail(cli, 1);
   ClearIPChecked(cli);
 
   /* Update the IP and charge them as a remote connect. */
@@ -1647,7 +1850,7 @@ static struct ConfItem *auth_find_class_conf(const char *class_name)
 
   /* Make sure the configuration class is valid. */
   class = find_class(class_name);
-  if (!class)
+  if (!class || !class->valid)
     return NULL;
 
   /* Look for an existing ConfItem for the class. */
@@ -1664,6 +1867,13 @@ static struct ConfItem *auth_find_class_conf(const char *class_name)
                            ConClass(class));
       return NULL;
     }
+    /* make_conf() "helpfully" links the conf into GlobalConfList,
+     * which we do not want, so undo that.  (Ugh.)
+     */
+    if (aconf == GlobalConfList) {
+      GlobalConfList = aconf->next;
+    }
+    /* Back to business as usual. */
     aconf->conn_class = class;
     aconf->next = aconf_list;
     aconf_list = aconf;
@@ -1672,13 +1882,31 @@ static struct ConfItem *auth_find_class_conf(const char *class_name)
   return aconf;
 }
 
+/** Tentatively accept a client in IAuth.
+ * @param[in] iauth Active IAuth session.
+ * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters.
+ * @param[in] params Optional class name for client.
+ * @return Negative (CPTR_KILLED) if the connection is refused, one otherwise.
+ */
+static int iauth_cmd_soft_done(struct IAuth *iauth, struct Client *cli,
+                              int parc, char **params)
+{
+  /* Clear iauth pending flag. */
+  assert(cli_auth(cli) != NULL);
+  FlagSet(&cli_auth(cli)->flags, AR_IAUTH_SOFT_DONE);
+  return 1;
+}
+
 /** Accept a client in IAuth.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters.
  * @param[in] params Optional class name for client.
- * @return One.
+ * @return Negative (CPTR_KILLED) if the connection is refused, one otherwise.
  */
-static int iauth_cmd_done_client(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_done_client(struct IAuth *iauth, struct Client *cli,
+                                int parc, char **params)
 {
   static time_t warn_time;
 
@@ -1687,16 +1915,31 @@ static int iauth_cmd_done_client(struct IAuth *iauth, struct Client *cli, char *
   FlagClr(&cli_auth(cli)->flags, AR_IAUTH_PENDING);
 
   /* If a connection class was specified (and usable), assign the client to it. */
-  if (!EmptyString(params)) {
+  if (!EmptyString(params[0])) {
     struct ConfItem *aconf;
 
-    aconf = auth_find_class_conf(params);
-    if (aconf)
-      attach_conf(cli, aconf);
-    else
+    aconf = auth_find_class_conf(params[0]);
+    if (aconf) {
+      enum AuthorizationCheckResult acr;
+
+      acr = attach_conf(cli, aconf);
+      switch (acr) {
+      case ACR_OK:
+        /* There should maybe be some way to set FLAG_DOID here.. */
+        break;
+      case ACR_TOO_MANY_IN_CLASS:
+        ++ServerStats->is_ref;
+        return exit_client(cli, cli, &me,
+                           "Sorry, your connection class is full - try "
+                           "again later or try another server");
+      default:
+        log_write(LS_IAUTH, L_ERROR, 0, "IAuth: Unexpected AuthorizationCheckResult %d from attach_conf()", acr);
+        break;
+      }
+    } else
       sendto_opmask_butone_ratelimited(NULL, SNO_AUTH, &warn_time,
                                        "iauth tried to use undefined class [%s]",
-                                       params);
+                                       params[0]);
   }
 
   return 1;
@@ -1705,208 +1948,290 @@ static int iauth_cmd_done_client(struct IAuth *iauth, struct Client *cli, char *
 /** Accept a client in IAuth and assign them to an account.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters.
  * @param[in] params Account name and optional class name for client.
- * @return Non-zero if \a cli authorization should be checked for completion.
+ * @return Negative if the connection is refused, otherwise non-zero
+ *   if \a cli authorization should be checked for completion.
  */
-static int iauth_cmd_done_account(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_done_account(struct IAuth *iauth, struct Client *cli,
+                                 int parc, char **params)
 {
-  char *end;
   size_t len;
 
   /* Sanity check. */
-  if (EmptyString(params)) {
+  if (EmptyString(params[0])) {
     sendto_iauth(cli, "E Missing :Missing account parameter");
     return 0;
   }
   /* Check length of account name. */
-  len = strcspn(params, ": ");
+  len = strcspn(params[0], ": ");
   if (len > ACCOUNTLEN) {
     sendto_iauth(cli, "E Invalid :Account parameter too long");
     return 0;
   }
   /* If account has a creation timestamp, use it. */
   assert(cli_user(cli) != NULL);
-  if (params[len] == ':')
-    cli_user(cli)->acc_create = strtoul(params + len + 1, &end, 10);
-  else
-    end = params + len;
+  if (params[0][len] == ':') {
+    cli_user(cli)->acc_create = strtoul(params[0] + len + 1, NULL, 10);
+    params[0][len] = '\0';
+  }
+
   /* Copy account name to User structure. */
-  ircd_strncpy(cli_user(cli)->account, params, ACCOUNTLEN);
+  ircd_strncpy(cli_user(cli)->account, params[0], ACCOUNTLEN);
   SetAccount(cli);
-  /* Skip whitespace before next argument. */
-  while (IsSpace(*end))
-    ++end;
+
   /* Fall through to the normal "done" handler. */
-  return iauth_cmd_done_client(iauth, cli, end);
+  return iauth_cmd_done_client(iauth, cli, parc - 1, params + 1);
 }
 
 /** Reject a client's connection.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Optional kill message.
  * @return Zero.
  */
-static int iauth_cmd_kill(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_kill(struct IAuth *iauth, struct Client *cli,
+                         int parc, char **params)
 {
   if (cli_auth(cli))
     FlagClr(&cli_auth(cli)->flags, AR_IAUTH_PENDING);
-  if (EmptyString(params))
-    params = "Access denied";
-  exit_client(cli, cli, &me, params);
+  if (EmptyString(params[0]))
+    params[0] = "Access denied";
+  exit_client(cli, cli, &me, params[0]);
   return 0;
 }
 
+/** Change a client's usermode.
+ * @param[in] iauth Active IAuth session.
+ * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (at least one).
+ * @param[in] params Usermode arguments for client (with the first
+ *   starting with '+').
+ * @return Zero.
+ */
+static int iauth_cmd_usermode(struct IAuth *iauth, struct Client *cli,
+                              int parc, char **params)
+{
+  if (params[0][0] == '+')
+  {
+    set_user_mode(cli, cli, parc + 2, params - 2, ALLOWMODES_ANY);
+  }
+  return 0;
+}
+
+
 /** Send a challenge string to the client.
  * @param[in] iauth Active IAuth session.
  * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (1).
  * @param[in] params Challenge message for client.
  * @return Zero.
  */
-static int iauth_cmd_challenge(struct IAuth *iauth, struct Client *cli, char *params)
+static int iauth_cmd_challenge(struct IAuth *iauth, struct Client *cli,
+                              int parc, char **params)
 {
-  sendrawto_one(cli, "NOTICE AUTH :*** %s", params);
+  if (!EmptyString(params[0]))
+    sendrawto_one(cli, "NOTICE AUTH :*** %s", params[0]);
   return 0;
 }
 
-/** Read input from \a iauth.
- * Reads up to SERVER_TCP_WINDOW bytes per pass.
- * @param[in] iauth Readable connection.
+/** Send an extension query to a specified remote server.
+ * @param[in] iauth Active IAuth session.
+ * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (3).
+ * @param[in] params Remote server, routing information, and query.
+ * @return Zero.
  */
-static void iauth_read(struct IAuth *iauth)
+static int iauth_cmd_xquery(struct IAuth *iauth, struct Client *cli,
+                           int parc, char **params)
 {
-  static char readbuf[SERVER_TCP_WINDOW];
+  char *serv;
+  const char *routing;
+  const char *query;
+  struct Client *acptr;
+
+  /* Process parameters */
+  if (EmptyString(params[0])) {
+    sendto_iauth(cli, "E Missing :Missing server parameter");
+    return 0;
+  } else
+    serv = params[0];
+
+  if (EmptyString(params[1])) {
+    sendto_iauth(cli, "E Missing :Missing routing parameter");
+    return 0;
+  } else
+    routing = params[1];
+
+  if (EmptyString(params[2])) {
+    sendto_iauth(cli, "E Missing :Missing query parameter");
+    return 0;
+  } else
+    query = params[2];
+
+  /* Try to find the specified server */
+  if (!(acptr = find_match_server(serv))) {
+    sendto_iauth(cli, "x %s %s :Server not online", serv, routing);
+    return 0;
+  }
+
+  /* If it's to us, do nothing; otherwise, forward the query */
+  if (!IsMe(acptr))
+    /* The "iauth:" prefix helps ircu route the reply to iauth */
+    sendcmdto_one(&me, CMD_XQUERY, acptr, "%C iauth:%s :%s", acptr, routing,
+                 query);
+
+  return 0;
+}
+
+/** Parse a \a message from \a iauth.
+ * @param[in] iauth Active IAuth session.
+ * @param[in] message Message to be parsed.
+ */
+static void iauth_parse(struct IAuth *iauth, char *message)
+{
+  char *params[MAXPARA + 1]; /* leave space for NULL */
+  int parc = 0;
   iauth_cmd_handler handler;
   struct AuthRequest *auth;
   struct Client *cli;
-  char *old_buffer;
-  char *params;
-  char *endp;
-  char *src;
-  unsigned int length;
   int has_cli;
-  int res;
   int id;
 
-  switch (os_recv_nonb(s_fd(i_socket(iauth)), readbuf, sizeof(readbuf), &length))
-  {
-  case IO_SUCCESS: break;
-  case IO_FAILURE: iauth_disconnect(iauth);
-  case IO_BLOCKED: return;
-  }
-  iauth->i_recvB += length;
-  old_buffer = iauth->i_buffer;
-  endp = old_buffer + iauth->i_count;
-  for (src = readbuf; length > 0; --length) {
-    *endp = *src++;
-    if (IsEol(*endp)) {
-      /* Terminate line, reset buffer and update statistics. */
-      *endp = '\0';
-      endp = old_buffer;
-      ++iauth->i_recvM;
-
-      /* If spammy debug, send the message to opers. */
-      if (i_debug(iauth) > 1)
-        sendto_opmask_butone(NULL, SNO_AUTH, "%s", endp);
-
-      /* Find command handler.  A lot of the handlers would be simpler
-       * with an argument splitter like in parse.c, but some commands
-       * (notably '>') do not use delimiters that way.
-       */
-      switch (*(endp = old_buffer)) {
-      case '>': handler = iauth_cmd_snotice; has_cli = 0; break;
-      case 'G': handler = iauth_cmd_debuglevel; has_cli = 0; break;
-      case 'O': handler = iauth_cmd_policy; has_cli = 0; break;
-      case 'V': handler = iauth_cmd_version; has_cli = 0; break;
-      case 'a': handler = iauth_cmd_newconfig; has_cli = 0; break;
-      case 'A': handler = iauth_cmd_config; has_cli = 0; break;
-      case 's': handler = iauth_cmd_newstats; has_cli = 0; break;
-      case 'S': handler = iauth_cmd_stats; has_cli = 0; break;
-      case 'o': handler = iauth_cmd_username_forced; has_cli = 1; break;
-      case 'U': handler = iauth_cmd_username_good; has_cli = 1; break;
-      case 'u': handler = iauth_cmd_username_bad; has_cli = 1; break;
-      case 'N': handler = iauth_cmd_hostname; has_cli = 1; break;
-      case 'I': handler = iauth_cmd_ip_address; has_cli = 1; break;
-      case 'C': handler = iauth_cmd_challenge; has_cli = 1; break;
-      case 'D': handler = iauth_cmd_done_client; has_cli = 1; break;
-      case 'R': handler = iauth_cmd_done_account; has_cli = 1; break;
-      case 'k': /* The 'k' command indicates the user should be booted
-                 * off without telling opers.  There is no way to
-                 * signal that to exit_client(), so we fall through to
-                 * the case that we do implement.
-                 */
-      case 'K': handler = iauth_cmd_kill; has_cli = 2; break;
-      case 'r': /* we handle termination directly */ continue;
-      default:  sendto_iauth(NULL, "E Garbage :[%s]", endp); continue;
-      }
+  /* Find command handler... */
+  switch (*(message++)) {
+  case '>': handler = iauth_cmd_snotice; has_cli = 0; break;
+  case 'G': handler = iauth_cmd_debuglevel; has_cli = 0; break;
+  case 'O': handler = iauth_cmd_policy; has_cli = 0; break;
+  case 'V': handler = iauth_cmd_version; has_cli = 0; break;
+  case 'a': handler = iauth_cmd_newconfig; has_cli = 0; break;
+  case 'A': handler = iauth_cmd_config; has_cli = 0; break;
+  case 's': handler = iauth_cmd_newstats; has_cli = 0; break;
+  case 'S': handler = iauth_cmd_stats; has_cli = 0; break;
+  case 'X': handler = iauth_cmd_xquery; has_cli = 0; break;
+  case 'o': handler = iauth_cmd_username_forced; has_cli = 1; break;
+  case 'U': handler = iauth_cmd_username_good; has_cli = 1; break;
+  case 'u': handler = iauth_cmd_username_bad; has_cli = 1; break;
+  case 'N': handler = iauth_cmd_hostname; has_cli = 1; break;
+  case 'I': handler = iauth_cmd_ip_address; has_cli = 1; break;
+  case 'M': handler = iauth_cmd_usermode; has_cli = 1; break;
+  case 'C': handler = iauth_cmd_challenge; has_cli = 1; break;
+  case 'd': handler = iauth_cmd_soft_done; has_cli = 1; break;
+  case 'D': handler = iauth_cmd_done_client; has_cli = 1; break;
+  case 'R': handler = iauth_cmd_done_account; has_cli = 1; break;
+  case 'k': /* The 'k' command indicates the user should be booted
+            * off without telling opers.  There is no way to
+            * signal that to exit_client(), so we fall through to
+            * the case that we do implement.
+            */
+  case 'K': handler = iauth_cmd_kill; has_cli = 2; break;
+  case 'r': /* we handle termination directly */ return;
+  default:  sendto_iauth(NULL, "E Garbage :[%s]", message); return;
+  }
 
-      /* Skip whitespace at start of arguments. */
-      while (IsSpace(*++endp)) ;
+  while (parc < MAXPARA) {
+    while (IsSpace(*message)) /* skip leading whitespace */
+      message++;
 
-      /* At this point, has_cli has one of three values:
-       * 0 - no client is identified
-       * 1 - a client is identified and must still be registering
-       * 2 - a client is identified but may be fully registered
-       */
+    if (!*message) /* hit the end of the string, break out */
+      break;
 
-      /* Figure out how to handle the command. */
-      if (!has_cli) {
-        /* Handler does not need a client. */
-        handler(iauth, NULL, endp);
-      } else {
-        /* Try to find the client associated with the request. */
-        id = strtol(endp, &params, 10);
-        while (IsSpace(*params))
-          ++params;
-        if (id < 0 || id > HighestFd || !(cli = LocalClientArray[id])) {
-          /* Client no longer exists (or never existed). */
-          sendto_iauth(NULL, "E Gone :[%s]", params);
-        } else if ((!(auth = cli_auth(cli))
-                    || !FlagHas(&auth->flags, AR_IAUTH_PENDING))
-                   && (has_cli == 1)) {
-          /* Client is done with IAuth checks. */
-          sendto_iauth(cli, "E Done :[%s]", params);
-        } else {
-          struct irc_sockaddr addr;
-          char *orig_ip;
-
-          /* Skip whitespace before IP address. */
-          while (IsSpace(*params))
-            ++params;
-          /* Record start of IP address, then null terminate it. */
-          orig_ip = params;
-          while (!IsSpace(*params) && *params != '\0')
-            ++params;
-          if (IsSpace(*params))
-            *params++ = '\0';
-          /* Parse out client IP address and port number. */
-          res = ipmask_parse(orig_ip, &addr.addr, NULL);
-          addr.port = strtol(params, &params, 10);
-          /* Skip whitespace and optional sentinel after port number. */
-          while (IsSpace(*params))
-            ++params;
-          if (*params == ':')
-            ++params;
-          /* Check IP address and port number against expected. */
-          if (0 == res
-              || irc_in_addr_cmp(&addr.addr, &cli_ip(cli))
-              || (auth && addr.port != auth->port)) {
-            /* Report mismatch to iauth. */
-            sendto_iauth(cli, "E Mismatch :[%s] != [%s]",
-                         orig_ip, ircd_ntoa(&cli_ip(cli)));
-          } else if (handler(iauth, cli, params)) {
-            /* Handler indicated a possible state change. */
-            check_auth_finished(auth, 0);
-          }
-        }
-      }
+    if (*message == ':') { /* found sentinel... */
+      params[parc++] = message + 1;
+      break; /* it's the last parameter anyway */
+    }
+
+    params[parc++] = message; /* save the parameter */
+    while (*message && !IsSpace(*message))
+      message++; /* find the end of the parameter */
 
-      /* Reset buffer pointer to read next line. */
-      endp = old_buffer;
+    if (*message) /* terminate the parameter */
+      *(message++) = '\0';
+  }
+
+  params[parc] = NULL; /* terminate the parameter list */
+
+  /* Check to see if the command specifies a client... */
+  if (!has_cli) {
+    /* Handler does not need a client. */
+    handler(iauth, NULL, parc, params);
+  } else {
+    /* Try to find the client associated with the request. */
+    id = strtol(params[0], NULL, 10);
+    if (parc < 3)
+      sendto_iauth(NULL, "E Missing :Need <id> <ip> <port>");
+    else if (id < 0 || id > HighestFd || !(cli = LocalClientArray[id]))
+      /* Client no longer exists (or never existed). */
+      sendto_iauth(NULL, "E Gone :[%s %s %s]", params[0], params[1],
+                  params[2]);
+    else if ((!(auth = cli_auth(cli)) ||
+             !FlagHas(&auth->flags, AR_IAUTH_PENDING)) &&
+            has_cli == 1)
+      /* Client is done with IAuth checks. */
+      sendto_iauth(cli, "E Done :[%s %s %s]", params[0], params[1], params[2]);
+    else {
+      struct irc_sockaddr addr;
+      int res;
+
+      /* Parse IP address and port number from parameters */
+      res = ipmask_parse(params[1], &addr.addr, NULL);
+      addr.port = strtol(params[2], NULL, 10);
+
+      /* Check IP address and port number against expected. */
+      if (0 == res ||
+         irc_in_addr_cmp(&addr.addr, &cli_ip(cli)) ||
+         (auth && addr.port != auth->port))
+       /* Report mismatch to iauth. */
+       sendto_iauth(cli, "E Mismatch :[%s] != [%s]", params[1],
+                    ircd_ntoa(&cli_ip(cli)));
+      else if (handler(iauth, cli, parc - 3, params + 3) > 0)
+       /* Handler indicated a possible state change. */
+       check_auth_finished(auth);
     }
-    else if (endp < old_buffer + BUFSIZE)
-      ++endp;
   }
-  iauth->i_count = endp - old_buffer;
+}
+
+/** Read input from \a iauth.
+ * Reads up to SERVER_TCP_WINDOW bytes per pass.
+ * @param[in] iauth Readable connection.
+ */
+static void iauth_read(struct IAuth *iauth)
+{
+  static char readbuf[SERVER_TCP_WINDOW];
+  unsigned int length, count;
+  char *sol;
+  char *eol;
+
+  /* Copy partial data to readbuf, append new data. */
+  length = iauth->i_count;
+  memcpy(readbuf, iauth->i_buffer, length);
+  if (IO_SUCCESS != os_recv_nonb(s_fd(i_socket(iauth)),
+                                readbuf + length,
+                                sizeof(readbuf) - length - 1,
+                                &count))
+    return;
+  readbuf[length += count] = '\0';
+
+  /* Parse each complete line. */
+  for (sol = readbuf; (eol = strchr(sol, '\n')) != NULL; sol = eol + 1) {
+    *eol = '\0';
+    if (*(eol - 1) == '\r') /* take out carriage returns, too... */
+      *(eol - 1) = '\0';
+
+    /* If spammy debug, send the message to opers. */
+    if (i_debug(iauth) > 1)
+      sendto_opmask_butone(NULL, SNO_AUTH, "Parsing: \"%s\"", sol);
+
+    /* Parse the line... */
+    iauth_parse(iauth, sol);
+  }
+
+  /* Put unused data back into connection's buffer. */
+  iauth->i_count = strlen(sol);
+  if (iauth->i_count > BUFSIZE)
+    iauth->i_count = BUFSIZE;
+  memcpy(iauth->i_buffer, sol, iauth->i_count);
 }
 
 /** Handle socket activity for an %IAuth connection.
@@ -1952,7 +2277,7 @@ static void iauth_sock_callback(struct Event *ev)
 static void iauth_read_stderr(struct IAuth *iauth)
 {
   static char readbuf[SERVER_TCP_WINDOW];
-  unsigned int length;
+  unsigned int length, count;
   char *sol;
   char *eol;
 
@@ -1962,13 +2287,15 @@ static void iauth_read_stderr(struct IAuth *iauth)
   if (IO_SUCCESS != os_recv_nonb(s_fd(i_stderr(iauth)),
                                  readbuf + length,
                                  sizeof(readbuf) - length - 1,
-                                 &length))
+                                 &count))
     return;
-  readbuf[length] = '\0';
+  readbuf[length += count] = '\0';
 
   /* Send each complete line to SNO_AUTH. */
   for (sol = readbuf; (eol = strchr(sol, '\n')) != NULL; sol = eol + 1) {
     *eol = '\0';
+    if (*(eol - 1) == '\r') /* take out carriage returns, too... */
+      *(eol - 1) = '\0';
     Debug((DEBUG_ERROR, "IAuth error: %s", sol));
     log_write(LS_IAUTH, L_ERROR, 0, "IAuth error: %s", sol);
     sendto_opmask_butone(NULL, SNO_AUTH, "%s", sol);
@@ -1994,14 +2321,17 @@ static void iauth_stderr_callback(struct Event *ev)
   assert(0 != iauth);
 
   switch (ev_type(ev)) {
+  case ET_DESTROY:
+    /* We do not restart iauth here: the stdout handler does that for us. */
+    break;
   case ET_READ:
     iauth_read_stderr(iauth);
     break;
   case ET_ERROR:
     log_write(LS_IAUTH, L_ERROR, 0, "IAuth stderr error: %s", strerror(ev_data(ev)));
-    /* and fall through to the ET_EOF/ET_DESTROY case */
-  case ET_DESTROY:
+    /* and fall through to the ET_EOF case */
   case ET_EOF:
+    iauth_disconnect(iauth);
     break;
   default:
     assert(0 && "Unrecognized event type");
@@ -2018,12 +2348,11 @@ void report_iauth_conf(struct Client *cptr, const struct StatDesc *sd, char *par
 {
     struct SLink *link;
 
-    for (link = iauth->i_config; link; link = link->next)
+    if (iauth) for (link = iauth->i_config; link; link = link->next)
     {
         send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":%s",
                    link->value.cp);
     }
-    send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":End of IAuth configuration.");
 }
 
 /** Report active iauth's statistics to \a cptr.
@@ -2035,10 +2364,9 @@ void report_iauth_conf(struct Client *cptr, const struct StatDesc *sd, char *par
 {
     struct SLink *link;
 
-    for (link = iauth->i_stats; link; link = link->next)
+    if (iauth) for (link = iauth->i_stats; link; link = link->next)
     {
         send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":%s",
                    link->value.cp);
     }
-    send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":End of IAuth statistics.");
 }