Fix buffer overflow in fr_pton_port
[freeradius.git] / src / lib / misc.c
index b2892f9..cbc38ea 100644 (file)
@@ -5,8 +5,7 @@
  *
  *   This library is free software; you can redistribute it and/or
  *   modify it under the terms of the GNU Lesser General Public
- *   the Free Software Foundation; either version 2 of the License, or (at
- *   your option) any later version. either
+ *   License as published by the Free Software Foundation; either
  *   version 2.1 of the License, or (at your option) any later version.
  *
  *   This library is distributed in the hope that it will be useful,
@@ -32,6 +31,28 @@ RCSID("$Id$")
 #include <pwd.h>
 #include <sys/uio.h>
 
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+
+/*
+ *     Some versions of Linux don't have closefrom(), but they will
+ *     have /proc.
+ *
+ *     BSD systems will generally have closefrom(), but not proc.
+ *
+ *     OSX doesn't have closefrom() or /proc/self/fd, but it does
+ *     have /dev/fd
+ */
+#ifdef __linux__
+#define CLOSEFROM_DIR "/proc/self/fd"
+#elif defined(__APPLE__)
+#define CLOSEFROM_DIR "/dev/fd"
+#else
+#undef HAVE_DIRENT_H
+#endif
+
+#endif
+
 #define FR_PUT_LE16(a, val)\
        do {\
                a[1] = ((uint16_t) (val)) >> 8;\
@@ -81,6 +102,28 @@ int fr_set_signal(int sig, sig_t func)
        return 0;
 }
 
+/** Uninstall a signal for a specific handler
+ *
+ * man sigaction says these are fine to call from a signal handler.
+ *
+ * @param sig SIGNAL
+ */
+int fr_unset_signal(int sig)
+{
+#ifdef HAVE_SIGACTION
+        struct sigaction act;
+
+        memset(&act, 0, sizeof(act));
+        act.sa_flags = 0;
+        sigemptyset(&act.sa_mask);
+        act.sa_handler = SIG_DFL;
+
+        return sigaction(sig, &act, NULL);
+#else
+        return signal(sig, SIG_DFL);
+#endif
+}
+
 static int _fr_trigger_talloc_ctx_free(fr_talloc_link_t *trigger)
 {
        if (trigger->armed) talloc_free(trigger->child);
@@ -199,10 +242,72 @@ char const *ip_ntoa(char *buffer, uint32_t ipaddr)
        return buffer;
 }
 
-/** Parse an IPv4 address or IPv4 prefix in presentation format (and others)
+/*
+ *     Parse decimal digits until we run out of decimal digits.
+ */
+static int ip_octet_from_str(char const *str, uint32_t *poctet)
+{
+       uint32_t octet;
+       char const *p = str;
+
+       if ((*p < '0') || (*p > '9')) {
+               return -1;
+       }
+
+       octet = 0;
+
+       while ((*p >= '0') && (*p <= '9')) {
+               octet *= 10;
+               octet += *p - '0';
+               p++;
+
+               if (octet > 255) return -1;
+       }
+
+
+       *poctet = octet;
+       return p - str;
+}
+
+static int ip_prefix_from_str(char const *str, uint32_t *paddr)
+{
+       int shift, length;
+       uint32_t octet;
+       uint32_t addr;
+       char const *p = str;
+
+       addr = 0;
+
+       for (shift = 24; shift >= 0; shift -= 8) {
+               length = ip_octet_from_str(p, &octet);
+               if (length <= 0) return -1;
+
+               addr |= octet << shift;
+               p += length;
+
+               /*
+                *      EOS or / means we're done.
+                */
+               if (!*p || (*p == '/')) break;
+
+               /*
+                *      We require dots between octets.
+                */
+               if (*p != '.') return -1;
+               p++;
+       }
+
+       *paddr = htonl(addr);
+       return p - str;
+}
+
+
+/**
+ * Parse an IPv4 address, IPv4 prefix in presentation format (and others), or
+ * a hostname.
  *
  * @param out Where to write the ip address value.
- * @param value to parse, may be dotted quad [+ prefix], or integer, or octal number, or '*' (INADDR_ANY).
+ * @param value to parse, may be dotted quad [+ prefix], or integer, or octal number, or '*' (INADDR_ANY), or a hostname.
  * @param inlen Length of value, if value is \0 terminated inlen may be -1.
  * @param resolve If true and value doesn't look like an IP address, try and resolve value as a hostname.
  * @param fallback to IPv6 resolution if no A records can be found.
@@ -211,11 +316,11 @@ char const *ip_ntoa(char *buffer, uint32_t ipaddr)
 int fr_pton4(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, bool fallback)
 {
        char *p;
-       unsigned int prefix;
+       unsigned int mask;
        char *eptr;
 
-       /* Dotted quad + / + [0-9]{1,2} */
-       char buffer[INET_ADDRSTRLEN + 3];
+       /* Dotted quad + / + [0-9]{1,2} or a hostname (RFC1035 2.3.4 Size limits) */
+       char buffer[256];
 
        /*
         *      Copy to intermediary buffer if we were given a length
@@ -227,9 +332,11 @@ int fr_pton4(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, b
                }
                memcpy(buffer, value, inlen);
                buffer[inlen] = '\0';
+               value = buffer;
        }
 
        p = strchr(value, '/');
+
        /*
         *      192.0.2.2 is parsed as if it was /32
         */
@@ -242,6 +349,7 @@ int fr_pton4(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, b
                 */
                if ((value[0] == '*') && (value[1] == '\0')) {
                        out->ipaddr.ip4addr.s_addr = htonl(INADDR_ANY);
+
                /*
                 *      Convert things which are obviously integers to IP addresses
                 *
@@ -250,9 +358,10 @@ int fr_pton4(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, b
                 */
                } else if (is_integer(value) || ((value[0] == '0') && (value[1] == 'x'))) {
                        out->ipaddr.ip4addr.s_addr = htonl(strtoul(value, NULL, 0));
+
                } else if (!resolve) {
                        if (inet_pton(AF_INET, value, &out->ipaddr.ip4addr.s_addr) <= 0) {
-                               fr_strerror_printf("Failed to parse IPv4 address string \"%s\"", value);
+                               fr_strerror_printf("Failed to parse IPv4 addreess string \"%s\"", value);
                                return -1;
                        }
                } else if (ip_hton(out, AF_INET, value, fallback) < 0) return -1;
@@ -261,48 +370,41 @@ int fr_pton4(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, b
        }
 
        /*
-        *      Otherwise parse the prefix
-        */
-       if ((size_t)(p - value) >= INET_ADDRSTRLEN) {
-               fr_strerror_printf("Invalid IPv4 address string \"%s\"", value);
-               return -1;
-       }
-
-       /*
         *      Copy the IP portion into a temporary buffer if we haven't already.
         */
        if (inlen < 0) memcpy(buffer, value, p - value);
        buffer[p - value] = '\0';
 
-       if (!resolve) {
-               if (inet_pton(AF_INET, buffer, &out->ipaddr.ip4addr.s_addr) <= 0) {
-                       fr_strerror_printf("Failed to parse IPv4 address string \"%s\"", value);
-                       return -1;
-               }
-       } else if (ip_hton(out, AF_INET, buffer, fallback) < 0) return -1;
+       if (ip_prefix_from_str(buffer, &out->ipaddr.ip4addr.s_addr) <= 0) {
+               fr_strerror_printf("Failed to parse IPv4 address string \"%s\"", value);
+               return -1;
+       }
 
-       prefix = strtoul(p + 1, &eptr, 10);
-       if (prefix > 32) {
+       mask = strtoul(p + 1, &eptr, 10);
+       if (mask > 32) {
                fr_strerror_printf("Invalid IPv4 mask length \"%s\".  Should be between 0-32", p);
                return -1;
        }
+
        if (eptr[0] != '\0') {
                fr_strerror_printf("Failed to parse IPv4 address string \"%s\", "
                                   "got garbage after mask length \"%s\"", value, eptr);
                return -1;
        }
 
-       if (prefix < 32) {
-               out->ipaddr.ip4addr = fr_inaddr_mask(&out->ipaddr.ip4addr, prefix);
+       if (mask < 32) {
+               out->ipaddr.ip4addr = fr_inaddr_mask(&out->ipaddr.ip4addr, mask);
        }
 
-       out->prefix = (uint8_t) prefix;
+       out->prefix = (uint8_t) mask;
        out->af = AF_INET;
 
        return 0;
 }
 
-/** Parse an IPv6 address or IPv6 prefix in presentation format (and others)
+/**
+ * Parse an IPv6 address or IPv6 prefix in presentation format (and others),
+ * or a hostname.
  *
  * @param out Where to write the ip address value.
  * @param value to parse.
@@ -317,8 +419,8 @@ int fr_pton6(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, b
        unsigned int prefix;
        char *eptr;
 
-       /* IPv6  + / + [0-9]{1,3} */
-       char buffer[INET6_ADDRSTRLEN + 4];
+       /* IPv6  + / + [0-9]{1,3} or a hostname (RFC1035 2.3.4 Size limits) */
+       char buffer[256];
 
        /*
         *      Copy to intermediary buffer if we were given a length
@@ -330,6 +432,7 @@ int fr_pton6(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, b
                }
                memcpy(buffer, value, inlen);
                buffer[inlen] = '\0';
+               value = buffer;
        }
 
        p = strchr(value, '/');
@@ -396,25 +499,29 @@ int fr_pton6(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve, b
 
 /** Simple wrapper to decide whether an IP value is v4 or v6 and call the appropriate parser.
  *
- * @param out Where to write the ip address value.
- * @param value to parse.
- * @param inlen Length of value, if value is \0 terminated inlen may be -1.
- * @param resolve If true and value doesn't look like an IP address, try and resolve value as a hostname.
- * @return 0 if ip address was parsed successfully, else -1 on error.
+ * @param[out] out Where to write the ip address value.
+ * @param[in] value to parse.
+ * @param[in] inlen Length of value, if value is \0 terminated inlen may be -1.
+ * @param[in] resolve If true and value doesn't look like an IP address, try and resolve value as a
+ *     hostname.
+ * @param[in] af If the address type is not obvious from the format, and resolve is true, the DNS
+ *     record (A or AAAA) we require.  Also controls which parser we pass the address to if
+ *     we have no idea what it is.
+ * @return
+ *     - 0 if ip address was parsed successfully.
+ *     - -1 on failure.
  */
-int fr_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve)
+int fr_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, int af, bool resolve)
 {
        size_t len, i;
 
        len = (inlen >= 0) ? (size_t)inlen : strlen(value);
        for (i = 0; i < len; i++) switch (value[i]) {
        /*
-        *      Chars illegal in domain names and IPv4 addresses.
+        *      ':' is illegal in domain names and IPv4 addresses.
         *      Must be v6 and cannot be a domain.
         */
        case ':':
-       case '[':
-       case ']':
                return fr_pton6(out, value, inlen, false, false);
 
        /*
@@ -430,8 +537,24 @@ int fr_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve)
                 *      Use A record in preference to AAAA record.
                 */
                if ((value[i] < '0') || (value[i] > '9')) {
-                       if (!resolve) return -1;
-                       return fr_pton4(out, value, inlen, true, true);
+                       if (!resolve) {
+                               fr_strerror_printf("Not IPv4/6 address, and asked not to resolve");
+                               return -1;
+                       }
+                       switch (af) {
+                       case AF_UNSPEC:
+                               return fr_pton4(out, value, inlen, resolve, true);
+
+                       case AF_INET:
+                               return fr_pton4(out, value, inlen, resolve, false);
+
+                       case AF_INET6:
+                               return fr_pton6(out, value, inlen, resolve, false);
+
+                       default:
+                               fr_strerror_printf("Invalid address family %i", af);
+                               return -1;
+                       }
                }
                break;
        }
@@ -443,6 +566,87 @@ int fr_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resolve)
        return fr_pton4(out, value, inlen, false, false);
 }
 
+/** Parses IPv4/6 address + port, to fr_ipaddr_t and integer
+ *
+ * @param[out] out Where to write the ip address value.
+ * @param[out] port_out Where to write the port (0 if no port found).
+ * @param[in] value to parse.
+ * @param[in] inlen Length of value, if value is \0 terminated inlen may be -1.
+ * @param[in] af If the address type is not obvious from the format, and resolve is true, the DNS
+ *     record (A or AAAA) we require.  Also controls which parser we pass the address to if
+ *     we have no idea what it is.
+ * @param[in] resolve If true and value doesn't look like an IP address, try and resolve value as a
+ *     hostname.
+ */
+int fr_pton_port(fr_ipaddr_t *out, uint16_t *port_out, char const *value, ssize_t inlen, int af, bool resolve)
+{
+       char const      *p = value, *q;
+       char            *end;
+       unsigned long   port;
+       char            buffer[6];
+       size_t          len;
+
+       *port_out = 0;
+
+       len = (inlen >= 0) ? (size_t)inlen : strlen(value);
+
+       if (*p == '[') {
+               if (!(q = memchr(p + 1, ']', len - 1))) {
+                       fr_strerror_printf("Missing closing ']' for IPv6 address");
+                       return -1;
+               }
+
+               /*
+                *      inet_pton doesn't like the address being wrapped in []
+                */
+               if (fr_pton6(out, p + 1, (q - p) - 1, false, false) < 0) return -1;
+
+               if (q[1] == ':') {
+                       q++;
+                       goto do_port;
+               }
+
+               return 0;
+       }
+
+       /*
+        *      Host, IPv4 or IPv6 with no port
+        */
+       q = memchr(p, ':', len);
+       if (!q) return fr_pton(out, p, len, af, resolve);
+
+       /*
+        *      IPv4 or host, with port
+        */
+       if (fr_pton(out, p, (q - p), af, resolve) < 0) return -1;
+
+do_port:
+       /*
+        *      Valid ports are a maximum of 5 digits, so if the
+        *      input length indicates there are more than 5 chars
+        *      after the ':' then there's an issue.
+        */
+       if (len > ((q + sizeof(buffer)) - value)) {
+       error:
+               fr_strerror_printf("IP string contains trailing garbage after port delimiter");
+               return -1;
+       }
+
+       p = q + 1;                      /* Move to first digit */
+
+       strlcpy(buffer, p, (len - (p - value)) + 1);
+       port = strtoul(buffer, &end, 10);
+       if (*end != '\0') goto error;   /* Trailing garbage after integer */
+
+       if ((port > UINT16_MAX) || (port == 0)) {
+               fr_strerror_printf("Port %lu outside valid port range 1-" STRINGIFY(UINT16_MAX), port);
+               return -1;
+       }
+       *port_out = port;
+
+       return 0;
+}
+
 int fr_ntop(char *out, size_t outlen, fr_ipaddr_t *addr)
 {
        char buffer[INET6_ADDRSTRLEN];
@@ -902,7 +1106,10 @@ int ip_hton(fr_ipaddr_t *out, int af, char const *hostname, bool fallback)
        rcode = fr_sockaddr2ipaddr((struct sockaddr_storage *)ai->ai_addr,
                                   ai->ai_addrlen, out, NULL);
        freeaddrinfo(res);
-       if (!rcode) return -1;
+       if (!rcode) {
+               fr_strerror_printf("Failed converting sockaddr to ipaddr");
+               return -1;
+       }
 
        return 0;
 }
@@ -1120,9 +1327,33 @@ bool is_whitespace(char const *value)
        return true;
 }
 
+/** Check whether the string is made up of printable UTF8 chars
+ *
+ * @param value to check.
+ * @param len of value.
+ *
+ * @return
+ *     - true if the string is printable.
+ *     - false if the string contains non printable chars
+ */
+ bool is_printable(void const *value, size_t len)
+ {
+       uint8_t const *p = value;
+       int     clen;
+       size_t  i;
+
+       for (i = 0; i < len; i++) {
+               clen = fr_utf8_char(p, len - i);
+               if (clen == 0) return false;
+               i += (size_t)clen;
+               p += clen;
+       }
+       return true;
+ }
+
 /** Check whether the string is all numbers
  *
- * @return true if the entirety of the string is are numebrs, else false.
+ * @return true if the entirety of the string is all numbers, else false.
  */
 bool is_integer(char const *value)
 {
@@ -1135,7 +1366,7 @@ bool is_integer(char const *value)
 
 /** Check whether the string is allzeros
  *
- * @return true if the entirety of the string is are numebrs, else false.
+ * @return true if the entirety of the string is all zeros, else false.
  */
 bool is_zero(char const *value)
 {
@@ -1154,20 +1385,63 @@ int closefrom(int fd)
 {
        int i;
        int maxfd = 256;
+#ifdef HAVE_DIRENT_H
+       DIR *dir;
+#endif
+
+#ifdef F_CLOSEM
+       if (fcntl(fd, F_CLOSEM) == 0) {
+               return 0;
+       }
+#endif
+
+#ifdef F_MAXFD
+       maxfd = fcntl(fd, F_F_MAXFD);
+       if (maxfd >= 0) goto do_close;
+#endif
 
 #ifdef _SC_OPEN_MAX
        maxfd = sysconf(_SC_OPEN_MAX);
        if (maxfd < 0) {
-         maxfd = 256;
+               maxfd = 256;
+       }
+#endif
+
+#ifdef HAVE_DIRENT_H
+       /*
+        *      Use /proc/self/fd directory if it exists.
+        */
+       dir = opendir(CLOSEFROM_DIR);
+       if (dir != NULL) {
+               long my_fd;
+               char *endp;
+               struct dirent *dp;
+
+               while ((dp = readdir(dir)) != NULL) {
+                       my_fd = strtol(dp->d_name, &endp, 10);
+                       if (my_fd <= 0) continue;
+
+                       if (*endp) continue;
+
+                       if (my_fd == dirfd(dir)) continue;
+
+                       if ((my_fd >= fd) && (my_fd <= maxfd)) {
+                               (void) close((int) my_fd);
+                       }
+               }
+               (void) closedir(dir);
+               return 0;
        }
 #endif
 
+#ifdef F_MAXFD
+do_close:
+#endif
+
        if (fd > maxfd) return 0;
 
        /*
         *      FIXME: return EINTR?
-        *
-        *      Use F_CLOSEM?
         */
        for (i = fd; i < maxfd; i++) {
                close(i);