Fix IP mask parsing some more (hopefully for good).
authorMichael Poole <mdpoole@troilus.org>
Mon, 12 Sep 2005 03:40:17 +0000 (03:40 +0000)
committerMichael Poole <mdpoole@troilus.org>
Mon, 12 Sep 2005 03:40:17 +0000 (03:40 +0000)
git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/trunk@1478 c9e4aea6-c8fd-4c43-8297-357d70d61c8c

ChangeLog
RELEASE.NOTES
include/ircd_string.h
include/match.h
ircd/ircd_string.c
ircd/m_burst.c
ircd/match.c
ircd/test/ircd_in_addr_t.c

index 5ba9007eef21d51dfce03a5cef9fbfdeb5a6aad5..11c1d561da176c123f2dfb2b7a2dd4b80bcf7b86 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,31 @@
+2005-09-11  Michael Poole <mdpoole@troilus.org>
+
+       * RELEASE.NOTES: Mention the side benefits of this change.
+
+       * include/ircd_string.h (ipmask_parse): Declare function here.
+       (ircd_aton): Becomes a special case of ipmask_parse().
+
+       * include/match.h (check_if_ipmask): Undeclare function.
+       (ipmask_parse): Remove function prototype from this file.
+
+       * ircd/ircd_string.c (ircd_aton_ip4): Add nullable pbits parameter
+       to accept ipmask length.  Rework to fill that in.
+       (ircd_aton): Rework into...
+       (ipmask_parse): this function, which knows how to fill in its own
+       pbits parameter.
+
+       * ircd/m_burst.c (ms_burst): Rely on make_ban() to set the ban
+       flags correctly, to avoid call to check_if_ipmask().
+
+       * ircd/match.c (ipmask_parse_ipv4): Delete function.
+       (check_if_ipmask): Likewise.
+       (ipmask_parse): Delete this version in favor of ircd_string.c's.
+
+       * ircd/test/ircd_in_addr_t.c (ipmask_test): New struct type.
+       (test_masks): New array of ipmask_test.
+       (test_ipmask): Function to run one of those tests.
+       (main): Call test_ipmask().
+
 2005-09-11  Alex Badea <vamposdecampos@gmail.com>
 
        * ircd/m_ping.c (ms_ping, mo_ping): misplaced chunk of code
index abdcf2b88256d61628c70c688e46679998954c76..bde760c11e15794faae4c7ff4ceca381084c1aee 100644 (file)
@@ -85,6 +85,10 @@ except X, you could use SILENCE *!*@*,~X!cservice@undernet.org.
 The server will no longer kick "net riders" in keyed (+k) channels if
 both sides of the net join have the same key.
 
+IP masks (as used in bans, G-lines, etc) are now parsed in a more
+forgiving manner.  127.0.0.0/8, 127.* and 127/8 are all accepted and
+mean the same thing.
+
 Configuration Changes:
 
 As mentioned above, the configuration file format has changed
index 7562135fc0b06da62373aff54ca79b5ab701c3db..df774c5e52b52f6a47b03c4af17c8896a2b61c27 100644 (file)
@@ -30,7 +30,8 @@ extern int         token_vector(char* names, char token,
                                 char** vector, int size);
 extern const char* ircd_ntoa(const struct irc_in_addr* addr);
 extern const char* ircd_ntoa_r(char* buf, const struct irc_in_addr* addr);
-extern int         ircd_aton(struct irc_in_addr *addr, const char *str);
+#define ircd_aton(ADDR, STR) ipmask_parse((STR), (ADDR), NULL)
+extern int ipmask_parse(const char *in, struct irc_in_addr *mask, unsigned char *bits_ptr);
 extern char*       host_from_uh(char* buf, const char* userhost, size_t len);
 extern char*       ircd_strtok(char** save, char* str, char* fs);
 
index 4af674794f8100407fb5e6e28fb59e332186893b..a2d7542ab973095e1f1e0dff1faa8dcf8b34759d 100644 (file)
@@ -29,8 +29,6 @@ extern int matchexec(const char *string, const char *cmask, int minlen);
 extern int matchdecomp(char *mask, const char *cmask);
 extern int mmexec(const char *wcm, int wminlen, const char *rcm, int rminlen);
 
-extern int check_if_ipmask(const char *mask);
-extern int ipmask_parse(const char *in, struct irc_in_addr *mask, unsigned char *bits_ptr);
 extern int ipmask_check(const struct irc_in_addr *addr, const struct irc_in_addr *mask, unsigned char bits);
 
 #endif /* INCLUDED_match_h */
index 1569349c28903d4773393eae29374681a5d35f2d..bfdceb5aac22c8bec4f660bab9e5c5865621fcc5 100644 (file)
@@ -453,12 +453,13 @@ const char* ircd_ntoa_r(char* buf, const struct irc_in_addr* in)
 /** Attempt to parse an IPv4 address into a network-endian form.
  * @param[in] input Input string.
  * @param[out] output Network-endian representation of the address.
+ * @param[out] pbits Number of bits found in pbits.
  * @return Number of characters used from \a input, or 0 if the parse failed.
  */
 static unsigned int
-ircd_aton_ip4(const char *input, unsigned int *output)
+ircd_aton_ip4(const char *input, unsigned int *output, unsigned char *pbits)
 {
-  unsigned int dots = 0, pos = 0, part = 0, ip = 0;
+  unsigned int dots = 0, pos = 0, part = 0, ip = 0, bits;
 
   /* Intentionally no support for bizarre IPv4 formats (plain
    * integers, octal or hex components) -- only vanilla dotted
@@ -466,33 +467,60 @@ ircd_aton_ip4(const char *input, unsigned int *output)
    */
   if (input[0] == '.')
     return 0;
-  while (1) {
-    if (IsDigit(input[pos])) {
-      part = part * 10 + input[pos++] - '0';
-      if (part > 255)
-        return 0;
-      if ((dots == 3) && !IsDigit(input[pos])) {
-        *output = htonl(ip | part);
-        return pos;
-      }
-    } else if (input[pos] == '.') {
-      if (input[++pos] == '.')
+  bits = 32;
+  while (1) switch (input[pos]) {
+  case '\0':
+    if (dots < 3)
+      return 0;
+  out:
+    ip |= part << (24 - 8 * dots);
+    *output = htonl(ip);
+    if (pbits)
+      *pbits = bits;
+    return pos;
+  case '.':
+    if (input[++pos] == '.')
+      return 0;
+    ip |= part << (24 - 8 * dots++);
+    part = 0;
+    if (input[pos] == '*') {
+      while (input[++pos] == '*') ;
+      if (input[pos] != '\0')
         return 0;
-      ip |= part << (24 - 8 * dots++);
-      part = 0;
-    } else
+      if (pbits)
+        *pbits = dots * 8;
+      *output = htonl(ip);
+      return pos;
+    }
+    break;
+  case '/':
+    if (!pbits || !IsDigit(input[pos + 1]))
+      return 0;
+    for (bits = 0; IsDigit(input[++pos]); )
+      bits = bits * 10 + input[pos] - '0';
+    if (bits > 32)
+      return 0;
+    goto out;
+  case '0': case '1': case '2': case '3': case '4':
+  case '5': case '6': case '7': case '8': case '9':
+    part = part * 10 + input[pos++] - '0';
+    if (part > 255)
       return 0;
+    break;
+  default:
+    return 0;
   }
 }
 
 /** Parse a numeric IPv4 or IPv6 address into an irc_in_addr.
- * @param[out] ip Receives parsed IP address.
  * @param[in] input Input buffer.
+ * @param[out] ip Receives parsed IP address.
+ * @param[out] pbits If non-NULL, receives number of bits specified in address mask.
  * @return Number of characters used from \a input, or 0 if the
  * address was unparseable or malformed.
  */
 int
-ircd_aton(struct irc_in_addr *ip, const char *input)
+ipmask_parse(const char *input, struct irc_in_addr *ip, unsigned char *pbits)
 {
   char *colon;
   char *dot;
@@ -520,80 +548,104 @@ ircd_aton(struct irc_in_addr *ip, const char *input)
       pos += 2;
       part_start = input + pos;
     }
-    while (ii < 8) {
+    while (ii < 8) switch (input[pos]) {
       unsigned char chval;
-
-      switch (input[pos]) {
-      case '0': case '1': case '2': case '3': case '4':
-      case '5': case '6': case '7': case '8': case '9':
-          chval = input[pos] - '0';
-      use_chval:
-        part = (part << 4) | chval;
-        if (part > 0xffff)
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+      chval = input[pos] - '0';
+    use_chval:
+      part = (part << 4) | chval;
+      if (part > 0xffff)
+        return 0;
+      pos++;
+      break;
+    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+      chval = input[pos] - 'A' + 10;
+      goto use_chval;
+    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+      chval = input[pos] - 'a' + 10;
+      goto use_chval;
+    case ':':
+      part_start = input + ++pos;
+      if (input[pos] == '.')
+        return 0;
+      ip->in6_16[ii++] = htons(part);
+      part = 0;
+      if (input[pos] == ':') {
+        if (colon < 8)
           return 0;
+        colon = ii;
         pos++;
-        break;
-      case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
-          chval = input[pos] - 'A' + 10;
-          goto use_chval;
-      case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
-          chval = input[pos] - 'a' + 10;
-          goto use_chval;
-      case ':':
-        part_start = input + ++pos;
-        if (input[pos] == '.')
-          return 0;
-        ip->in6_16[ii++] = htons(part);
-        part = 0;
-        if (input[pos] == ':') {
-          if (colon < 8)
-            return 0;
-          colon = ii;
-          pos++;
-        }
-        break;
-      case '.': {
-        uint32_t ip4;
-        unsigned int len;
-        len = ircd_aton_ip4(part_start, &ip4);
-        if (!len || (ii > 6))
-          return 0;
-        ip->in6_16[ii++] = htons(ntohl(ip4) >> 16);
-        ip->in6_16[ii++] = htons(ntohl(ip4) & 65535);
-        if (colon < 8) {
-          unsigned int jj;
-          /* Shift stuff after "::" up and fill middle with zeros. */
-          for (jj = 0; jj < ii - colon; jj++)
-            ip->in6_16[7 - jj] = ip->in6_16[ii - jj - 1];
-          for (jj = 0; jj < 8 - ii; jj++)
-            ip->in6_16[colon + jj] = 0;
-        }
-        return part_start - input + len;
-      }
-      default: {
-        ip->in6_16[ii++] = htons(part);
-        if (colon < 8) {
-          unsigned int jj;
-          /* Shift stuff after "::" up and fill middle with zeros. */
-          for (jj = 0; jj < ii - colon; jj++)
-            ip->in6_16[7 - jj] = ip->in6_16[ii - jj - 1];
-          for (jj = 0; jj < 8 - ii; jj++)
-            ip->in6_16[colon + jj] = 0;
-        }
-        return pos;
-      }
       }
+      break;
+    case '.': {
+      uint32_t ip4;
+      unsigned int len;
+      len = ircd_aton_ip4(part_start, &ip4, pbits);
+      if (!len || (ii > 6))
+        return 0;
+      ip->in6_16[ii++] = htons(ntohl(ip4) >> 16);
+      ip->in6_16[ii++] = htons(ntohl(ip4) & 65535);
+      if (pbits)
+        *pbits += 96;
+      pos = part_start + len - input;
+      goto finish;
+    }
+    case '/':
+      if (!pbits || !IsDigit(input[pos + 1]))
+        return 0;
+      ip->in6_16[ii++] = htons(part);
+      for (part = 0; IsDigit(input[++pos]); )
+        part = part * 10 + input[pos] - '0';
+      if (part > 128)
+        return 0;
+      *pbits = part;
+      goto finish;
+    case '*':
+      while (input[++pos] == '*') ;
+      if (input[pos] != '\0' || colon < 8)
+        return 0;
+      if (pbits)
+        *pbits = ii * 16;
+      return pos;
+    case '\0':
+      ip->in6_16[ii++] = htons(part);
+      if (colon == 8 && ii < 8)
+        return 0;
+      if (pbits)
+        *pbits = 128;
+      goto finish;
+    default:
+      return 0;
+    }
+  finish:
+    if (colon < 8) {
+      unsigned int jj;
+      /* Shift stuff after "::" up and fill middle with zeros. */
+      for (jj = 0; jj < ii - colon; jj++)
+        ip->in6_16[7 - jj] = ip->in6_16[ii - jj - 1];
+      for (jj = 0; jj < 8 - ii; jj++)
+        ip->in6_16[colon + jj] = 0;
     }
     return pos;
   } else if (dot) {
     unsigned int addr;
-    int len = ircd_aton_ip4(input, &addr);
+    int len = ircd_aton_ip4(input, &addr, pbits);
     if (len) {
       ip->in6_16[5] = htons(65535);
       ip->in6_16[6] = htons(ntohl(addr) >> 16);
       ip->in6_16[7] = htons(ntohl(addr) & 65535);
-      return len;
+      if (pbits)
+        *pbits += 96;
     }
-  }
-  return 0; /* parse failed */
+    return len;
+  } else if (input[0] == '*') {
+    unsigned int pos = 0;
+    while (input[++pos] == '*') ;
+    if (input[pos] != '\0')
+      return 0;
+    if (pbits)
+      *pbits = 0;
+    return pos;
+  } else return 0; /* parse failed */
 }
index 50c59b6da1a3c05d42883587c69f9145d35635d1..5914af44cbc63641c47ce31707fbbbb4ab8f29cc 100644 (file)
@@ -371,11 +371,7 @@ int ms_burst(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
            newban = make_ban(ban); /* create new ban */
             strcpy(newban->who, "*");
            newban->when = TStime();
-
-           newban->flags = BAN_BURSTED; /* set flags */
-           if ((ptr = strrchr(ban, '@')) && check_if_ipmask(ptr + 1))
-             newban->flags |= BAN_IPMASK;
-
+           newban->flags |= BAN_BURSTED;
            newban->next = 0;
            if (lp)
              lp->next = newban; /* link it in */
index 68f57d1f130a92fa020c2bfdc0f8165b2c369f12..1715b539458b12ea3f1f9e87dc88fdb55eb9de58 100644 (file)
@@ -839,102 +839,6 @@ int mmexec(const char *wcm, int wminlen, const char *rcm, int rminlen)
   return 1;                     /* Auch... something left out ? Fail */
 }
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-/** Parse an input string as an IPv4 address.
- * @param[in] in Text form of address.
- * @param[out] out IPv4 address in network representation.
- * @return Number of address bits specified by \a in.
- */
-static int ipmask_parse_ipv4(const char *in, struct in_addr *out)
-{
-  int class;
-  int ad[4] = { 0 };
-  int bits = 0;
-
-  class = sscanf(in, "%d.%d.%d.%d/%d", &ad[0], &ad[1], &ad[2], &ad[3], &bits);
-  if (class != 5)
-    bits = class * 8;
-  out->s_addr = ntohl((ad[0] << 24) | (ad[1] << 16) | (ad[2] << 8) | ad[3]);
-  return bits;
-}
-
-/** Test whether a string looks like it matches only IPv4 addresses.
- * @param[in] mask Hostname matching mask.
- * @return Non-zero if \a mask can only match IPv4 addresses, zero otherwise.
- */
-int check_if_ipmask(const char *mask)
-{
-  int has_digit = 0;
-  const char *p;
-
-  /* Given the bug that inspired this test, this may seem like a hasty
-   * kludge.  It isn't: Wildcard characters should be matched from the
-   * start, as when the username is the "interesting" part of the ban.
-   * Likewise, we cannot simply reject masks interpreted as x/0 for
-   * all x.
-   */
-  if (mask[0] == '.' || mask[0] == '/')
-    return 0;
-  for (p = mask; *p; ++p)
-    if (*p != '*' && *p != '.' && *p != '/')
-    {
-      if (!IsDigit(*p))
-        return 0;
-      has_digit = -1;
-    }
-
-  return has_digit;
-}
-
-/** Try to parse an IPv4 or IPv6 address mask.
- * @param[in] in Address matching mask.
- * @param[out] mask Fixed bits of address mask.
- * @param[out] bits_ptr If non-NULL, receives number of bits specified in address mask.
- * @return Non-zero on successful parse; zero on error.
- */
-int ipmask_parse(const char *in, struct irc_in_addr *mask, unsigned char *bits_ptr)
-{
-  struct in_addr ipv4;
-  char *p;
-  int bits = 0;
-
-  if (check_if_ipmask(in)) {
-    bits = ipmask_parse_ipv4(in, &ipv4);
-    mask->in6_16[0] = mask->in6_16[1] = mask->in6_16[2] = 0;
-    mask->in6_16[3] = mask->in6_16[4] = 0;
-    mask->in6_16[5] = 0xffff;
-    memcpy(&mask->in6_16[6], &ipv4.s_addr, sizeof(ipv4.s_addr));
-    bits += 96;
-  } else if (in[0] == '*' && in[1] == '\0') {
-    /* accept as a 0-bit mask */
-    memset(&mask->in6_16, 0, sizeof(mask->in6_16));
-    bits = 0;
-  } else {
-    if (!(p = strchr(in, '/')))
-      bits = 128;
-    else
-      *p = 0;
-    if (!ircd_aton(mask, in)) {
-      if (p)
-        *p = '/';
-      return 0;
-    }
-    if (p) {
-      bits = atoi(p + 1);
-      *p = '/';
-    }
-  }
-
-  if (bits_ptr)
-    *bits_ptr = bits;
-  return 1;
-}
-
 /** Test whether an address matches the most significant bits of a mask.
  * @param[in] addr Address to test.
  * @param[in] mask Address to test against.
index 2797057c10b85e9ac90f396ceaf1c08d68eace12..07f6cc96abe9f479be4bfc97e014922a5e7b71d5 100644 (file)
@@ -95,13 +95,74 @@ test_address(struct address_test *addr)
     printf("Passed: %s (%s/%s)\n", addr->text, base64_v4, base64_v6);
 }
 
+/** Structure to describe a test for IP mask parsing. */
+struct ipmask_test {
+    const char *text; /**< Textual IP mask to parse. */
+    struct irc_in_addr expected; /**< Canonical form of address. */
+    unsigned int is_ipmask : 1; /**< Parse as an ipmask? */
+    unsigned int is_parseable : 1; /**< Is address parseable? */
+    unsigned int exp_bits : 8; /**< Number of bits expected in netmask */
+};
+
+/** Array of ipmasks to test with. */
+static struct ipmask_test test_masks[] = {
+    { "::", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 128 },
+    { "::/0", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 0 },
+    { "::10.0.0.0", {{ 0, 0, 0, 0, 0, 0, 0xa00, 0 }}, 1, 1, 128 },
+    { "192.168/16", {{ 0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0 }}, 1, 1, 112 },
+    { "192.*", {{ 0, 0, 0, 0, 0, 0xffff, 0xc000, 0 }}, 1, 1, 104 },
+    { "192.*/8", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 },
+    { "192*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 },
+    { "192.168.0.0/16", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 0, 0, 0 },
+    { "ab.*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 },
+    { "a*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 },
+    { "*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 0 },
+    { "a:b", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 },
+    { "a::*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 },
+    { "a:*", {{ 0xa, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 16 },
+    { "a:/16", {{ 0xa, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 16 },
+    { 0 }
+};
+
+/** Perform tests for a single IP mask.
+ * @param[in] mask IP mask test structure.
+ */
+static void
+test_ipmask(struct ipmask_test *mask)
+{
+    struct irc_in_addr parsed;
+    unsigned int len, ii;
+    unsigned char bits = 0;
+
+    /* Convert expected address to network order. */
+    for (ii = 0; ii < 8; ++ii)
+        mask->expected.in6_16[ii] = htons(mask->expected.in6_16[ii]);
+    /* Try to parse; make sure its parseability and netmask length are
+     * as expected. */
+    len = ipmask_parse(mask->text, &parsed, mask->is_ipmask ? &bits : 0);
+    assert(!!len == mask->is_parseable);
+    if (!len) {
+        printf("X-Fail: %s\n", mask->text);
+        return;
+    }
+    if (mask->is_ipmask)
+        assert(bits == mask->exp_bits);
+    assert(!memcmp(&parsed, &mask->expected, sizeof(parsed)));
+    printf("Passed: %s (%s/%u)\n", mask->text, ircd_ntoa(&parsed), bits);
+}
+
 int
 main(int argc, char *argv[])
 {
     unsigned int ii;
 
+    printf("Testing address parsing..\n");
     for (ii = 0; test_addrs[ii].text; ++ii)
         test_address(&test_addrs[ii]);
 
+    printf("\nTesting ipmask parsing..\n");
+    for (ii = 0; test_masks[ii].text; ++ii)
+        test_ipmask(&test_masks[ii]);
+
     return 0;
 }