Make realname Kill blocks more predictable, and add username="x" field.
[ircu2.10.12-pk.git] / ircd / s_conf.c
index 11a8c77cd4da01b44154bc36cf23f939d6a0e69b..51c0971cafda3d0c6aa76c32e3ff7ff3bafa8b9f 100644 (file)
@@ -56,8 +56,7 @@
 #include "struct.h"
 #include "sys.h"
 
-#include <assert.h>
-#include <arpa/inet.h>
+/* #include <assert.h> -- Now using assert in ircd_log.h */
 #include <errno.h>
 #include <fcntl.h>
 #include <netdb.h>
@@ -69,7 +68,7 @@
 
 /** Global list of all ConfItem structures. */
 struct ConfItem  *GlobalConfList;
-/** Count of items in #GlobalConfLis. */
+/** Count of items in #GlobalConfList. */
 int              GlobalConfCount;
 /** Global list of service mappings. */
 struct s_map     *GlobalServiceMapList;
@@ -151,11 +150,14 @@ void free_conf(struct ConfItem *aconf)
          aconf->address.port));
   if (aconf->dns_pending)
     delete_resolver_queries(aconf);
+  MyFree(aconf->username);
   MyFree(aconf->host);
+  MyFree(aconf->origin_name);
   if (aconf->passwd)
     memset(aconf->passwd, 0, strlen(aconf->passwd));
   MyFree(aconf->passwd);
   MyFree(aconf->name);
+  MyFree(aconf->hub_limit);
   MyFree(aconf);
 #ifdef        DEBUGMODE
   --GlobalConfCount;
@@ -194,6 +196,38 @@ static void detach_conf(struct Client* cptr, struct ConfItem* aconf)
   }
 }
 
+/** Parse a user\@host mask into username and host or IP parts.
+ * If \a host contains no username part, set \a aconf->username to
+ * NULL.  If the host part of \a host looks like an IP mask, set \a
+ * aconf->addrbits and \a aconf->address to match.  Otherwise, set
+ * \a aconf->host, and set \a aconf->addrbits to -1.
+ * @param[in,out] aconf Configuration item to set.
+ * @param[in] host user\@host mask to parse.
+ */
+void conf_parse_userhost(struct ConfItem *aconf, char *host)
+{
+  char *host_part;
+  unsigned char addrbits;
+
+  MyFree(aconf->username);
+  MyFree(aconf->host);
+  host_part = strchr(host, '@');
+  if (host_part) {
+    *host_part = '\0';
+    DupString(aconf->username, host);
+    host_part++;
+  } else {
+    aconf->username = NULL;
+    host_part = host;
+  }
+  DupString(aconf->host, host_part);
+  if (ipmask_parse(aconf->host, &aconf->address.addr, &addrbits))
+    aconf->addrbits = addrbits;
+  else
+    aconf->addrbits = -1;
+  MyFree(host);
+}
+
 /** Copies a completed DNS query into its ConfItem.
  * @param vptr Pointer to struct ConfItem for the block.
  * @param hp DNS reply, or NULL if the lookup failed.
@@ -322,8 +356,9 @@ void det_confs_butmask(struct Client* cptr, int mask)
 }
 
 /** Check client limits and attach Client block.
- * If the password field consists of one or two digits, use that
- * as the per-IP connection limit; otherwise use 255.
+ * If there are more connections from the IP than \a aconf->maximum
+ * allows, return ACR_TOO_MANY_FROM_IP.  Otherwise, attach \a aconf to
+ * \a cptr.
  * @param cptr Client getting \a aconf.
  * @param aconf Configuration item to attach.
  * @return Authorization check result.
@@ -331,16 +366,7 @@ void det_confs_butmask(struct Client* cptr, int mask)
 static enum AuthorizationCheckResult
 check_limit_and_attach(struct Client* cptr, struct ConfItem* aconf)
 {
-  int number = 255;
-  
-  if (aconf->passwd) {
-    if (IsDigit(*aconf->passwd) && !aconf->passwd[1])
-      number = *aconf->passwd-'0';
-    else if (IsDigit(*aconf->passwd) && IsDigit(aconf->passwd[1]) && 
-             !aconf->passwd[2])
-      number = (*aconf->passwd-'0')*10+(aconf->passwd[1]-'0');
-  }
-  if (IPcheck_nr(cptr) > number)
+  if (IPcheck_nr(cptr) > aconf->maximum)
     return ACR_TOO_MANY_FROM_IP;
   return attach_conf(cptr, aconf);
 }
@@ -349,11 +375,9 @@ check_limit_and_attach(struct Client* cptr, struct ConfItem* aconf)
  * @param cptr Client for whom to check rules.
  * @return Authorization check result.
  */
-enum AuthorizationCheckResult attach_iline(struct Client*  cptr)
+enum AuthorizationCheckResult attach_iline(struct Client* cptr)
 {
   struct ConfItem* aconf;
-  static char      uhost[HOSTLEN + USERLEN + 3];
-  static char      fullname[HOSTLEN + 1];
   struct DNSReply* hp;
 
   assert(0 != cptr);
@@ -362,47 +386,121 @@ enum AuthorizationCheckResult attach_iline(struct Client*  cptr)
   for (aconf = GlobalConfList; aconf; aconf = aconf->next) {
     if (aconf->status != CONF_CLIENT)
       continue;
+    /* If you change any of this logic, please make corresponding
+     * changes in conf_debug_iline() below.
+     */
     if (aconf->address.port && aconf->address.port != cli_listener(cptr)->addr.port)
       continue;
-    if (!aconf->host || !aconf->name)
+    if (aconf->username) {
+      SetFlag(cptr, FLAG_DOID);
+      if (match(aconf->username, cli_username(cptr)))
+        continue;
+    }
+    if (aconf->host && (!hp || match(aconf->host, hp->h_name)))
       continue;
-    if (hp) {
-      ircd_strncpy(fullname, hp->h_name, HOSTLEN);
-      fullname[HOSTLEN] = '\0';
+    if ((aconf->addrbits >= 0)
+        && !ipmask_check(&cli_ip(cptr), &aconf->address.addr, aconf->addrbits))
+      continue;
+    return check_limit_and_attach(cptr, aconf);
+  }
+  return ACR_NO_AUTHORIZATION;
+}
 
-      Debug((DEBUG_DNS, "a_il: %s->%s", cli_sockhost(cptr), fullname));
+/** Interpret \a client as a client specifier and show which Client
+ * block(s) match that client.
+ *
+ * The client specifier may contain an IP address, hostname, listener
+ * port, or a combination of those separated by commas.  IP addresses
+ * and hostnamese may be preceded by "username@"; the last given
+ * username will be used for the match.
+ *
+ * @param[in] client Client specifier.
+ * @return Matching Client block structure.
+ */
+struct ConfItem *conf_debug_iline(const char *client)
+{
+  struct irc_in_addr address;
+  struct ConfItem *aconf;
+  char *sep;
+  unsigned short listener;
+  char username[USERLEN+1], hostname[HOSTLEN+1];
+
+  /* Initialize variables. */
+  listener = 0;
+  memset(&address, 0, sizeof(address));
+  memset(&username, 0, sizeof(username));
+  memset(&hostname, 0, sizeof(hostname));
+
+  /* Parse client specifier. */
+  while (*client) {
+    struct irc_in_addr tmpaddr;
+    long tmp;
+
+    /* Try to parse as listener port number first. */
+    tmp = strtol(client, &sep, 10);
+    if (tmp && (*sep == '\0' || *sep == ',')) {
+      listener = tmp;
+      client = sep + (*sep != '\0');
+      continue;
+    }
 
-      if (strchr(aconf->name, '@')) {
-        strcpy(uhost, cli_username(cptr));
-        strcat(uhost, "@");
-      }
-      else
-        *uhost = '\0';
-      strncat(uhost, fullname, sizeof(uhost) - 1 - strlen(uhost));
-      uhost[sizeof(uhost) - 1] = 0;
-      if (0 == match(aconf->name, uhost)) {
-        if (strchr(uhost, '@'))
-          SetFlag(cptr, FLAG_DOID);
-        return check_limit_and_attach(cptr, aconf);
-      }
+    /* Maybe username@ before an IP address or hostname? */
+    tmp = strcspn(client, ",@");
+    if (client[tmp] == '@') {
+      if (tmp > USERLEN)
+        tmp = USERLEN;
+      ircd_strncpy(username, client, tmp);
+      /* and fall through */
+      client += tmp + 1;
     }
-    if (strchr(aconf->host, '@')) {
-      ircd_strncpy(uhost, cli_username(cptr), sizeof(uhost) - 2);
-      uhost[sizeof(uhost) - 2] = 0;
-      strcat(uhost, "@");
+
+    /* Looks like an IP address? */
+    tmp = ircd_aton(&tmpaddr, client);
+    if (tmp && (client[tmp] == '\0' || client[tmp] == ',')) {
+        memcpy(&address, &tmpaddr, sizeof(address));
+        client += tmp + (client[tmp] != '\0');
+        continue;
     }
-    else
-      *uhost = '\0';
-    strncat(uhost, cli_sock_ip(cptr), sizeof(uhost) - 1 - strlen(uhost));
-    uhost[sizeof(uhost) - 1] = 0;
-    if (match(aconf->host, uhost))
-      continue;
-    if (strchr(uhost, '@'))
-      SetFlag(cptr, FLAG_DOID);
 
-    return check_limit_and_attach(cptr, aconf);
+    /* Else must be a hostname. */
+    tmp = strcspn(client, ",");
+    if (tmp > HOSTLEN)
+      tmp = HOSTLEN;
+    ircd_strncpy(hostname, client, tmp);
+    client += tmp + (client[tmp] != '\0');
   }
-  return ACR_NO_AUTHORIZATION;
+
+  /* Walk configuration to find matching Client block. */
+  for (aconf = GlobalConfList; aconf; aconf = aconf->next) {
+    if (aconf->status != CONF_CLIENT)
+      continue;
+    if (aconf->address.port && aconf->address.port != listener) {
+      fprintf(stdout, "Listener port mismatch: %u != %u\n", aconf->address.port, listener);
+      continue;
+    }
+    if (aconf->username && match(aconf->username, username)) {
+      fprintf(stdout, "Username mismatch: %s != %s\n", aconf->username, username);
+      continue;
+    }
+    if (aconf->host && match(aconf->host, hostname)) {
+      fprintf(stdout, "Hostname mismatch: %s != %s\n", aconf->host, hostname);
+      continue;
+    }
+    if ((aconf->addrbits >= 0)
+        && !ipmask_check(&address, &aconf->address.addr, aconf->addrbits)) {
+      fprintf(stdout, "IP address mismatch: %s != %s\n", aconf->name, ircd_ntoa(&address));
+      continue;
+    }
+    fprintf(stdout, "Match! username=%s host=%s ip=%s class=%s maxlinks=%u password=%s\n",
+            (aconf->username ? aconf->username : "(null)"),
+            (aconf->host ? aconf->host : "(null)"),
+            (aconf->name ? aconf->name : "(null)"),
+            ConfClass(aconf), aconf->maximum,  aconf->passwd);
+    return aconf;
+  }
+
+  fprintf(stdout, "No matches found.\n");
+  return NULL;
 }
 
 /** Check whether a particular ConfItem is already attached to a
@@ -424,7 +522,7 @@ static int is_attached(struct ConfItem *aconf, struct Client *cptr)
 
 /** Associate a specific configuration entry to a *local* client (this
  * is the one which used in accepting the connection). Note, that this
- * automaticly changes the attachment if there was an old one...
+ * automatically changes the attachment if there was an old one...
  * @param cptr Client to attach \a aconf to
  * @param aconf ConfItem to attach
  * @return Authorization check result.
@@ -520,41 +618,33 @@ struct ConfItem* attach_confs_byhost(struct Client* cptr, const char* host,
 /** Find a ConfItem that has the same name and user+host fields as
  * specified.  Requires an exact match for \a name.
  * @param name Name to match
- * @param user User part of match (or NULL)
- * @param host Hostname part of match
+ * @param cptr Client to match against
  * @param statmask Filter for ConfItem::status
  * @return First found matching ConfItem.
  */
-struct ConfItem* find_conf_exact(const char* name, const char* user,
-                                 const char* host, int statmask)
+struct ConfItem* find_conf_exact(const char* name, struct Client *cptr, int statmask)
 {
   struct ConfItem *tmp;
-  char userhost[USERLEN + HOSTLEN + 3];
-
-  if (user)
-    ircd_snprintf(0, userhost, sizeof(userhost), "%s@%s", user, host);
-  else
-    ircd_strncpy(userhost, host, sizeof(userhost) - 1);
 
   for (tmp = GlobalConfList; tmp; tmp = tmp->next) {
     if (!(tmp->status & statmask) || !tmp->name || !tmp->host ||
         0 != ircd_strcmp(tmp->name, name))
       continue;
-    /*
-     * Accept if the *real* hostname (usually sockecthost)
-     * socket host) matches *either* host or name field
-     * of the configuration.
-     */
-    if (match(tmp->host, userhost))
+    if (tmp->username
+        && (EmptyString(cli_username(cptr))
+            || match(tmp->username, cli_username(cptr))))
       continue;
-    if (tmp->status & CONF_OPERATOR) {
-      if (tmp->clients < MaxLinks(tmp->conn_class))
-        return tmp;
-      else
+    if (tmp->addrbits < 0)
+    {
+      if (match(tmp->host, cli_sockhost(cptr)))
         continue;
     }
-    else
-      return tmp;
+    else if (!ipmask_check(&cli_ip(cptr), &tmp->address.addr, tmp->addrbits))
+      continue;
+    if ((tmp->status & CONF_OPERATOR)
+        && (tmp->clients >= MaxLinks(tmp->conn_class)))
+      continue;
+    return tmp;
   }
   return 0;
 }
@@ -666,6 +756,7 @@ void conf_erase_deny_list(void)
     MyFree(p->hostmask);
     MyFree(p->usermask);
     MyFree(p->message);
+    MyFree(p->realmask);
     MyFree(p);
   }
   denyConfList = 0;
@@ -746,6 +837,17 @@ yyerror(const char *msg)
  conf_error = 1;
 }
 
+/** Attach CONF_UWORLD items to a server and everything attached to it. */
+static void
+attach_conf_uworld(struct Client *cptr)
+{
+  struct DLink *lp;
+
+  attach_confs_byhost(cptr, cli_name(cptr), CONF_UWORLD);
+  for (lp = cli_serv(cptr)->down; lp; lp = lp->next)
+    attach_conf_uworld(lp->value.cptr);
+}
+
 /** Reload the configuration file.
  * @param cptr Client that requested rehash (if a signal, &me).
  * @param sig Type of rehash (0 = oper-requested, 1 = signal, 2 =
@@ -830,10 +932,8 @@ int rehash(struct Client *cptr, int sig)
   for (i = 0; i <= HighestFd; i++) {
     if ((acptr = LocalClientArray[i])) {
       assert(!IsMe(acptr));
-      if (IsServer(acptr)) {
+      if (IsServer(acptr))
         det_confs_butmask(acptr, ~(CONF_UWORLD | CONF_ILLEGAL));
-        attach_confs_byname(acptr, cli_name(acptr), CONF_UWORLD);
-      }
       /* Because admin's are getting so uppity about people managing to
        * get past K/G's etc, we'll "fix" the bug by actually explaining
        * whats going on.
@@ -851,6 +951,8 @@ int rehash(struct Client *cptr, int sig)
     }
   }
 
+  attach_conf_uworld(&me);
+
   return ret;
 }
 
@@ -915,28 +1017,16 @@ int find_kill(struct Client *cptr)
    *             -- Isomer
    */
   for (deny = denyConfList; deny; deny = deny->next) {
-    if (0 != match(deny->usermask, name))
+    if (deny->usermask && match(deny->usermask, name))
+      continue;
+    if (deny->realmask && match(deny->realmask, realname))
+      continue;
+    if (deny->bits > 0) {
+      if (!ipmask_check(&cli_ip(cptr), &deny->address, deny->bits))
+        continue;
+    } else if (deny->hostmask && match(deny->hostmask, host))
       continue;
 
-    if (EmptyString(deny->hostmask))
-      break;
-
-    if (deny->flags & DENY_FLAGS_REALNAME) { /* K: by real name */
-      if (0 == match(deny->hostmask + 2, realname))
-       break;
-    } else if (deny->flags & DENY_FLAGS_IP) { /* k: by IP */
-#ifdef DEBUGMODE
-      char tbuf1[SOCKIPLEN], tbuf2[SOCKIPLEN];
-      Debug((DEBUG_DEBUG, "ip: %s network: %s/%u",
-             ircd_ntoa_r(tbuf1, &cli_ip(cptr)), ircd_ntoa_r(tbuf2, &deny->address), deny->bits));
-#endif
-      if (ipmask_check(&cli_ip(cptr), &deny->address, deny->bits))
-        break;
-    }
-    else if (0 == match(deny->hostmask, host))
-      break;
-  }
-  if (deny) {
     if (EmptyString(deny->message))
       send_reply(cptr, SND_EXPLICIT | ERR_YOUREBANNEDCREEP,
                  ":Connection from your host is refused on this server.");
@@ -946,19 +1036,17 @@ int find_kill(struct Client *cptr)
       else
         send_reply(cptr, SND_EXPLICIT | ERR_YOUREBANNEDCREEP, ":%s.", deny->message);
     }
+    return -1;
   }
-  else if ((agline = gline_lookup(cptr, 0))) {
+
+  if ((agline = gline_lookup(cptr, 0))) {
     /*
      * find active glines
      * added a check against the user's IP address to find_gline() -Kev
      */
     send_reply(cptr, SND_EXPLICIT | ERR_YOUREBANNEDCREEP, ":%s.", GlineReason(agline));
-  }
-
-  if (deny)
-    return -1;
-  if (agline)
     return -2;
+  }
 
   return 0;
 }
@@ -1061,7 +1149,6 @@ int conf_check_server(struct Client *cptr)
    * attach the Connect block to the client structure for later use.
    */
   attach_conf(cptr, c_conf);
-  attach_confs_byname(cptr, cli_name(cptr), CONF_UWORLD);
 
   if (!irc_in_addr_valid(&c_conf->address.addr))
     memcpy(&c_conf->address.addr, &cli_ip(cptr), sizeof(c_conf->address.addr));