Fix SF bug #2694761 (iauth class assignment did not work correctly).
[ircu2.10.12-pk.git] / ircd / s_auth.c
index bb6f30a5e5db65c21a1c6a0a4843a1e087e94308..b578c43dfde8561ab448c4923775d95f439ad773 100644 (file)
@@ -117,6 +117,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")
@@ -131,6 +132,7 @@ typedef enum {
   REPORT_DO_ID,
   REPORT_FIN_ID,
   REPORT_FAIL_ID,
+  REPORT_FAIL_IAUTH,
   REPORT_IP_MISMATCH,
   REPORT_INVAL_DNS
 } ReportType;
@@ -198,7 +200,9 @@ struct IAuth {
 #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);
@@ -381,12 +385,14 @@ static int check_auth_finished(struct AuthRequest *auth)
    * as possible so that iauth's challenge/response (which uses PASS
    * for responses) is not confused with the client's password.
    */
-  if (!FlagHas(&auth->flags, AR_PASSWORD_CHECKED))
+  if (IsUserPort(auth->client)
+      && !FlagHas(&auth->flags, AR_PASSWORD_CHECKED))
   {
     struct ConfItem *aconf;
 
     aconf = cli_confs(auth->client)->value.aconf;
-    if (!EmptyString(aconf->passwd)
+    if (aconf
+        && !EmptyString(aconf->passwd)
         && strcmp(cli_passwd(auth->client), aconf->passwd))
     {
       ServerStats->is_ref++;
@@ -421,13 +427,17 @@ static int check_auth_finished(struct AuthRequest *auth)
   else
     FlagSet(&auth->flags, AR_IAUTH_HURRY);
 
-  destroy_auth_request(auth);
-  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;
 }
 
@@ -451,6 +461,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.
@@ -463,6 +493,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:
@@ -715,7 +749,58 @@ void destroy_auth_request(struct AuthRequest* auth)
 
   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)) {
+      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.
@@ -736,14 +821,6 @@ static void auth_timeout_callback(struct Event* ev)
     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);
@@ -753,6 +830,7 @@ static void auth_timeout_callback(struct Event* ev)
 
     /* 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);
@@ -781,21 +859,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. */
@@ -920,8 +995,13 @@ void start_auth(struct Client* 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;
@@ -934,9 +1014,7 @@ 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;
@@ -981,6 +1059,7 @@ 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);
 }
@@ -988,10 +1067,14 @@ int auth_set_pong(struct AuthRequest *auth, unsigned int cookie)
 /** 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;
 
@@ -1004,7 +1087,7 @@ 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);
@@ -1266,18 +1349,22 @@ 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. */
@@ -1358,6 +1445,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;
 }
@@ -1672,6 +1760,14 @@ static int iauth_cmd_hostname(struct IAuth *iauth, struct Client *cli,
   }
   /* Set hostname from params. */
   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;
 }
 
@@ -1733,7 +1829,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. */
@@ -1750,6 +1846,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;
@@ -1763,7 +1866,7 @@ static struct ConfItem *auth_find_class_conf(const char *class_name)
  * @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,
                                 int parc, char **params)
@@ -1779,9 +1882,24 @@ static int iauth_cmd_done_client(struct IAuth *iauth, struct Client *cli,
     struct ConfItem *aconf;
 
     aconf = auth_find_class_conf(params[0]);
-    if (aconf)
-      attach_conf(cli, aconf);
-    else
+    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[0]);
@@ -1795,7 +1913,8 @@ static int iauth_cmd_done_client(struct IAuth *iauth, struct Client *cli,
  * @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,
                                  int parc, char **params)
@@ -1844,6 +1963,25 @@ static int iauth_cmd_kill(struct IAuth *iauth, struct Client *cli,
   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.
@@ -1888,6 +2026,7 @@ static void iauth_parse(struct IAuth *iauth, char *message)
   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_done_client; has_cli = 1; break;
   case 'R': handler = iauth_cmd_done_account; has_cli = 1; break;
@@ -1930,7 +2069,9 @@ static void iauth_parse(struct IAuth *iauth, char *message)
   } else {
     /* Try to find the client associated with the request. */
     id = strtol(params[0], NULL, 10);
-    if (id < 0 || id > HighestFd || !(cli = LocalClientArray[id]))
+    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]);
@@ -1954,7 +2095,7 @@ static void iauth_parse(struct IAuth *iauth, char *message)
        /* 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))
+      else if (handler(iauth, cli, parc - 3, params + 3) > 0)
        /* Handler indicated a possible state change. */
        check_auth_finished(auth);
     }
@@ -2090,14 +2231,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");
@@ -2119,7 +2263,6 @@ void report_iauth_conf(struct Client *cptr, const struct StatDesc *sd, char *par
         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.
@@ -2136,5 +2279,4 @@ void report_iauth_conf(struct Client *cptr, const struct StatDesc *sd, char *par
         send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":%s",
                    link->value.cp);
     }
-    send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":End of IAuth statistics.");
 }