ipmask_parse: Reject a full IPv6 address followed by ::.
[ircu2.10.12-pk.git] / ircd / ircd_string.c
index 8f0cd09563cf6bb1b594382eedecafae8c4b06c5..ed0349ea45fc20b1d8700cc80c355b82317c0603 100644 (file)
@@ -30,7 +30,6 @@
 
 /* #include <assert.h> -- Now using assert in ircd_log.h */
 #include <string.h>
-#include <regex.h>
 #include <sys/types.h>
 #include <netinet/in.h>
 
  */
 #include "chattr.tab.c"
 
-
-/*
- * Disallow a hostname label to contain anything but a [-a-zA-Z0-9].
- * It may not start or end on a '.'.
- * A label may not end on a '-', the maximum length of a label is
- * 63 characters.
- * On top of that (which seems to be the RFC) we demand that the
- * top domain does not contain any digits.
- */
-/** Regular expresion to match a hostname.
- * Matches zero or more alphanumeric labels followed by '.' and a
- * final label that may only contain alphabetic characters.
- */
-static const char* hostExpr = "^([-0-9A-Za-z]*[0-9A-Za-z]\\.)+[A-Za-z]+$";
-/** Compiled regex to match a hostname.  Built from #hostExpr. */
-static regex_t hostRegex;
-
-/** Regular expression to match an IP address. */
-static const char* addrExpr =
-    "^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\\.){1,3}"
-    "(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$";
-/** Compiled regex to match an IP address.  Built from #addrExpr. */
-static regex_t addrRegex;
-
-/** Initialize the string matching code. */
-int init_string(void)
-{
-  /*
-   * initialize matching expressions
-   * XXX - expressions MUST be correct, don't change expressions
-   * without testing them. Might be a good idea to exit if these fail,
-   * important code depends on them.
-   * TODO: use regerror for an error message
-   */
-  if (regcomp(&hostRegex, hostExpr, REG_EXTENDED | REG_NOSUB))
-    return 0;
-
-  if (regcomp(&addrRegex, addrExpr, REG_EXTENDED | REG_NOSUB))
-    return 0;
-  return 1;
-}
-
-/** Check whether \a str looks like a hostname.
- * @param[in] str String that might be a hostname.
- * @return Non-zero if it conforms to the rules, zero if not.
- */
-int string_is_hostname(const char* str)
-{
-  assert(0 != str);
-  return (strlen(str) <= HOSTLEN && 0 == regexec(&hostRegex, str, 0, 0, 0));
-}
-
-/** Check whether \a str looks like an IP address.
- * @param[in] str String that might be an address.
- * @return Non-zero if it conforms to the rules, zero if not.
- */
-int string_is_address(const char* str)
-{
-  assert(0 != str);
-  return (0 == regexec(&addrRegex, str, 0, 0, 0));
-}
-
 /** Check whether \a str contains wildcard characters.
  * @param[in] str String that might contain wildcards.
  * @return Non-zero if \a str contains naked (non-escaped) wildcards,
@@ -219,6 +156,8 @@ char* ircd_strncpy(char* s1, const char* s2, size_t n)
 
   while (s < endp && (*s++ = *s2++))
     ;
+  if (s == endp)
+    *s = '\0';
   return s1;
 }
 
@@ -232,21 +171,6 @@ NTL_HDR_strCasediff { NTL_SRC_strCasediff }
  * Other functions visible externally
  */
 
-/** Find common character attributes for the start of a string.
- * @param[in] s Input string to scan.
- * @param[in] n Maximum number of bytes to check.
- * @return Bitmask of all character attributes shared by the start of \a s.
- */
-int strnChattr(const char *s, const size_t n)
-{
-  const char *rs = s;
-  unsigned int x = ~0;
-  int r = n;
-  while (*rs && r--)
-    x &= IRCD_CharAttrTab[*rs++ - CHAR_MIN];
-  return x;
-}
-
 /** Case insensitive string comparison.
  * @param[in] a First string to compare.
  * @param[in] b Second string to compare.
@@ -531,12 +455,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
@@ -544,33 +469,62 @@ 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 (++dots > 3)
+      return 0;
+    if (input[++pos] == '.')
+      return 0;
+    ip |= part << (32 - 8 * dots);
+    part = 0;
+    if (input[pos] == '*') {
+      while (input[++pos] == '*' || 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;
@@ -589,7 +543,7 @@ ircd_aton(struct irc_in_addr *ip, const char *input)
      * This is pretty straightforward; the only trick is borrowed
      * from Paul Vixie (BIND): when it sees a "::" continue as if
      * it were a single ":", but note where it happened, and fill
-     * with zeros afterwards.
+     * with zeros afterward.
      */
     if (input[pos] == ':') {
       if ((input[pos+1] != ':') || (input[pos+2] == ':'))
@@ -598,79 +552,110 @@ 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)
-          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] == '.')
+    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;
-        ip->in6_16[ii++] = htons(part);
-        part = 0;
-        if (input[pos] == ':') {
-          if (colon < 8)
+        if (ii == 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;
-      }
+        colon = ii;
+        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] == '*' || input[pos] == ':') ;
+      if (input[pos] != '\0' || colon < 8)
+        return 0;
+      if (part && ii < 8)
+          ip->in6_16[ii++] = htons(part);
+      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;
+    }
+    if (input[pos] != '\0')
+      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) {
+  } else if (dot || strchr(input, '/')) {
     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 */
 }