Add fr_abin2hex
[freeradius.git] / src / lib / misc.c
index 6d6e7e8..84d1624 100644 (file)
@@ -35,7 +35,6 @@ RCSID("$Id$")
        } while (0)
 
 #ifdef HAVE_PTHREAD_H
-static pthread_mutex_t autofree_context = PTHREAD_MUTEX_INITIALIZER;
 #  define PTHREAD_MUTEX_LOCK pthread_mutex_lock
 #  define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
 #else
@@ -53,6 +52,11 @@ static char const *months[] = {
 
 fr_thread_local_setup(char *, fr_inet_ntop_buffer);    /* macro */
 
+typedef struct fr_talloc_link {
+       bool armed;
+       TALLOC_CTX *child;
+} fr_talloc_link_t;
+
 /** Sets a signal handler using sigaction if available, else signal
  *
  * @param sig to set handler for.
@@ -81,25 +85,50 @@ int fr_set_signal(int sig, sig_t func)
        return 0;
 }
 
-/** Allocates a new talloc context from the root autofree context
+static int _fr_trigger_talloc_ctx_free(fr_talloc_link_t *trigger)
+{
+       if (trigger->armed) talloc_free(trigger->child);
+
+       return 0;
+}
+
+static int _fr_disarm_talloc_ctx_free(bool **armed)
+{
+       **armed = false;
+       return 0;
+}
+
+/** Link a parent and a child context, so the child is freed before the parent
  *
- * This function is threadsafe, whereas using the NULL context is not.
+ * @note This is not thread safe. Do not free parent before threads are joined, do not call from a child thread.
+ * @note It's OK to free the child before threads are joined, but this will leak memory until the parent is freed.
  *
- * @note The returned context must be freed by the caller.
- * @returns a new talloc context parented by the root autofree context.
+ * @param parent who's fate the child should share.
+ * @param child bound to parent's lifecycle.
+ * @return 0 on success -1 on failure.
  */
-TALLOC_CTX *fr_autofree_ctx(void)
+int fr_link_talloc_ctx_free(TALLOC_CTX *parent, TALLOC_CTX *child)
 {
-       static TALLOC_CTX *ctx = NULL, *child;
-       PTHREAD_MUTEX_LOCK(&autofree_context);
-       if (!ctx) {
-               ctx = talloc_autofree_context();
+       fr_talloc_link_t *trigger;
+       bool **disarm;
+
+       trigger = talloc(parent, fr_talloc_link_t);
+       if (!trigger) return -1;
+
+       disarm = talloc(child, bool *);
+       if (!disarm) {
+               talloc_free(trigger);
+               return -1;
        }
 
-       child = talloc_new(ctx);
-       PTHREAD_MUTEX_UNLOCK(&autofree_context);
+       trigger->child = child;
+       trigger->armed = true;
+       *disarm = &trigger->armed;
+
+       talloc_set_destructor(trigger, _fr_trigger_talloc_ctx_free);
+       talloc_set_destructor(disarm, _fr_disarm_talloc_ctx_free);
 
-       return child;
+       return 0;
 }
 
 /*
@@ -180,9 +209,10 @@ char const *ip_ntoa(char *buffer, uint32_t ipaddr)
  * @param value to parse, may be dotted quad [+ prefix], or integer, or octal number, or '*' (INADDR_ANY).
  * @param inlen Length of value, if value is \0 terminated inlen may be 0.
  * @param resolve If true and value doesn't look like an IP address, try and resolve value as a hostname.
+ * @param fallback to IPv4 resolution if no A records can be found.
  * @return 0 if ip address was parsed successfully, else -1 on error.
  */
-int fr_pton(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
+int fr_pton4(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve, bool fallback)
 {
        char *p;
        unsigned int prefix;
@@ -219,14 +249,14 @@ int fr_pton(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
                 *      We assume the number is the bigendian representation of the
                 *      IP address.
                 */
-               } else if (is_integer(value)) {
+               } 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);
                                return -1;
                        }
-               } else if (ip_hton(AF_INET, value, out) < 0) return -1;
+               } else if (ip_hton(out, AF_INET, value, fallback) < 0) return -1;
 
                out->prefix = 32;
                out->af = AF_INET;
@@ -253,7 +283,7 @@ int fr_pton(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
                        fr_strerror_printf("Failed to parse IPv4 address string \"%s\"", value);
                        return -1;
                }
-       } else if (ip_hton(AF_INET, buffer, out) < 0) return -1;
+       } else if (ip_hton(out, AF_INET, buffer, fallback) < 0) return -1;
 
        prefix = strtoul(p + 1, &eptr, 10);
        if (prefix > 32) {
@@ -267,7 +297,7 @@ int fr_pton(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
        }
 
        if (prefix < 32) {
-               out->ipaddr.ip4addr = fr_ipaddr_mask(&(out->ipaddr.ip4addr), prefix);
+               out->ipaddr.ip4addr = fr_inaddr_mask(&(out->ipaddr.ip4addr), prefix);
        }
 
        out->prefix = (uint8_t) prefix;
@@ -282,9 +312,10 @@ int fr_pton(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
  * @param value to parse.
  * @param inlen Length of value, if value is \0 terminated inlen may be 0.
  * @param resolve If true and value doesn't look like an IP address, try and resolve value as a hostname.
+ * @param fallback to IPv4 resolution if no AAAA records can be found.
  * @return 0 if ip address was parsed successfully, else -1 on error.
  */
-int fr_pton6(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
+int fr_pton6(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve, bool fallback)
 {
        char const *p;
        unsigned int prefix;
@@ -317,7 +348,7 @@ int fr_pton6(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
                                fr_strerror_printf("Failed to parse IPv6 address string \"%s\"", value);
                                return -1;
                        }
-               } else if (ip_hton(AF_INET6, value, out) < 0) return -1;
+               } else if (ip_hton(out, AF_INET6, value, fallback) < 0) return -1;
 
                out->prefix = 128;
                out->af = AF_INET6;
@@ -341,7 +372,7 @@ int fr_pton6(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
                        fr_strerror_printf("Failed to parse IPv6 address string \"%s\"", value);
                        return -1;
                }
-       } else if (ip_hton(AF_INET6, buffer, out) < 0) return -1;
+       } else if (ip_hton(out, AF_INET6, buffer, fallback) < 0) return -1;
 
        prefix = strtoul(p + 1, &eptr, 10);
        if (prefix > 128) {
@@ -357,7 +388,7 @@ int fr_pton6(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
        if (prefix < 128) {
                struct in6_addr addr;
 
-               addr = fr_ipaddr_mask6(&(out->ipaddr.ip6addr), prefix);
+               addr = fr_in6addr_mask(&(out->ipaddr.ip6addr), prefix);
                memcpy(&(out->ipaddr.ip6addr.s6_addr), &addr, sizeof(addr));
        }
 
@@ -367,6 +398,86 @@ int fr_pton6(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
        return 0;
 }
 
+/** 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 0.
+ * @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.
+ */
+int fr_pton(fr_ipaddr_t *out, char const *value, size_t inlen, bool resolve)
+{
+       size_t len, i;
+
+       len = (inlen == 0) ? strlen(value) : inlen;
+       for (i = 0; i < len; i++) switch (value[i]) {
+       /*
+        *      Chars 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);
+
+       /*
+        *      Chars which don't really tell us anything
+        */
+       case '.':
+       case '/':
+               continue;
+
+       default:
+               /*
+                *      Outside the range of IPv4 chars, must be a domain
+                *      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);
+               }
+               break;
+       }
+
+       /*
+        *      All chars were in the IPv4 set [0-9/.], must be an IPv4
+        *      address.
+        */
+       return fr_pton4(out, value, inlen, false, false);
+}
+
+/** Check if the IP address is equivalent to INADDR_ANY
+ *
+ * @param addr to chec.
+ * @return true if IP address matches INADDR_ANY or INADDR6_ANY (assumed to be 0), else false.
+ */
+bool is_wildcard(fr_ipaddr_t *addr)
+{
+       static struct in6_addr in6_addr;
+
+       switch (addr->af) {
+       case AF_INET:
+               return (addr->ipaddr.ip4addr.s_addr == htons(INADDR_ANY));
+
+       case AF_INET6:
+               return (memcmp(addr->ipaddr.ip6addr.s6_addr, in6_addr.s6_addr, sizeof(in6_addr.s6_addr)) == 0) ? true :false;
+
+       default:
+               fr_assert(0);
+               return false;
+       }
+}
+
+int fr_ntop(char *out, size_t outlen, fr_ipaddr_t *addr)
+{
+       char buffer[INET6_ADDRSTRLEN];
+
+       if (inet_ntop(addr->af, &(addr->ipaddr), buffer, sizeof(buffer)) == NULL) return -1;
+
+       return snprintf(out, outlen, "%s/%i", buffer, addr->prefix);
+}
+
 /*
  *     Internal wrapper for locking, to minimize the number of ifdef's
  *
@@ -712,46 +823,34 @@ char const *inet_ntop(int af, void const *src, char *dst, size_t cnt)
 }
 #endif
 
-
-/*
- *     Try to convert the address to v4 then v6
- */
-int ip_ptonx(char const *src, fr_ipaddr_t *dst)
-{
-       if (inet_pton(AF_INET, src, &dst->ipaddr.ip4addr) == 1) {
-               dst->af = AF_INET;
-               return 1;
-       }
-
-#ifdef HAVE_STRUCT_SOCKADDR_IN6
-       if (inet_pton(AF_INET6, src, &dst->ipaddr.ip6addr) == 1) {
-               dst->af = AF_INET6;
-               return 1;
-       }
-#endif
-
-       dst->scope = 0;
-
-       return 0;
-}
-
-/*
- *     Wrappers for IPv4/IPv6 host to IP address lookup.
- *     This API returns only one IP address, of the specified
- *     address family, or the first address (of whatever family),
- *     if AF_UNSPEC is used.
+/** Wrappers for IPv4/IPv6 host to IP address lookup
+ *
+ * This function returns only one IP address, of the specified address family,
+ * or the first address (of whatever family), if AF_UNSPEC is used.
+ *
+ * If fallback is specified and af is AF_INET, but no AF_INET records were
+ * found and a record for AF_INET6 exists that record will be returned.
+ *
+ * If fallback is specified and af is AF_INET6, and a record with AF_INET4 exists
+ * that record will be returned instead.
+ *
+ * @param out Where to write result.
+ * @param af To search for in preference.
+ * @param hostname to search for.
+ * @param fallback to the other adress family, if no records matching af, found.
+ * @return 0 on success, else -1 on failure.
  */
-int ip_hton(int af, char const *src, fr_ipaddr_t *out)
+int ip_hton(fr_ipaddr_t *out, int af, char const *hostname, bool fallback)
 {
        int rcode;
-       struct addrinfo hints, *ai = NULL, *res = NULL;
+       struct addrinfo hints, *ai = NULL, *alt = NULL, *res = NULL;
 
        if (!fr_hostname_lookups) {
 #ifdef HAVE_STRUCT_SOCKADDR_IN6
                if (af == AF_UNSPEC) {
                        char const *p;
 
-                       for (p = src; *p != '\0'; p++) {
+                       for (p = hostname; *p != '\0'; p++) {
                                if ((*p == ':') ||
                                    (*p == '[') ||
                                    (*p == ']')) {
@@ -764,7 +863,7 @@ int ip_hton(int af, char const *src, fr_ipaddr_t *out)
 
                if (af == AF_UNSPEC) af = AF_INET;
 
-               if (!inet_pton(af, src, &(out->ipaddr))) {
+               if (!inet_pton(af, hostname, &(out->ipaddr))) {
                        return -1;
                }
 
@@ -784,24 +883,25 @@ int ip_hton(int af, char const *src, fr_ipaddr_t *out)
                /*
                 *      If it's all numeric, avoid getaddrinfo()
                 */
-               if (inet_pton(af, src, &out->ipaddr.ip4addr) == 1) {
+               if (inet_pton(af, hostname, &out->ipaddr.ip4addr) == 1) {
                        return 0;
                }
        }
 #endif
 
-       if ((rcode = getaddrinfo(src, NULL, &hints, &res)) != 0) {
+       if ((rcode = getaddrinfo(hostname, NULL, &hints, &res)) != 0) {
                fr_strerror_printf("ip_hton: %s", gai_strerror(rcode));
                return -1;
        }
 
        for (ai = res; ai; ai = ai->ai_next) {
-               if ((af == ai->ai_family) || (af == AF_UNSPEC))
-                       break;
+               if ((af == ai->ai_family) || (af == AF_UNSPEC)) break;
+               if (!alt && fallback && ((ai->ai_family == AF_INET) || (ai->ai_family == AF_INET6))) alt = ai;
        }
 
+       if (!ai) ai = alt;
        if (!ai) {
-               fr_strerror_printf("ip_hton failed to find requested information for host %.100s", src);
+               fr_strerror_printf("ip_hton failed to find requested information for host %.100s", hostname);
                freeaddrinfo(ai);
                return -1;
        }
@@ -848,7 +948,7 @@ char const *ip_ntoh(fr_ipaddr_t const *src, char *dst, size_t cnt)
  * @param prefix Number of contiguous bits to mask.
  * @return an ipv6 address with the host portion zeroed out.
  */
-struct in_addr fr_ipaddr_mask(struct in_addr const *ipaddr, uint8_t prefix)
+struct in_addr fr_inaddr_mask(struct in_addr const *ipaddr, uint8_t prefix)
 {
        uint32_t ret;
 
@@ -871,7 +971,7 @@ struct in_addr fr_ipaddr_mask(struct in_addr const *ipaddr, uint8_t prefix)
  * @param prefix Number of contiguous bits to mask.
  * @return an ipv6 address with the host portion zeroed out.
  */
-struct in6_addr fr_ipaddr_mask6(struct in6_addr const *ipaddr, uint8_t prefix)
+struct in6_addr fr_in6addr_mask(struct in6_addr const *ipaddr, uint8_t prefix)
 {
        uint64_t const *p = (uint64_t const *) ipaddr;
        uint64_t ret[2], *o = ret;
@@ -897,31 +997,62 @@ struct in6_addr fr_ipaddr_mask6(struct in6_addr const *ipaddr, uint8_t prefix)
        return *(struct in6_addr *) &ret;
 }
 
-static char const *hextab = "0123456789abcdef";
+/** Zeroes out the host portion of an fr_ipaddr_t
+ *
+ * @param[in,out] addr to mask
+ * @param[in] prefix Length of the network portion.
+ */
+void fr_ipaddr_mask(fr_ipaddr_t *addr, uint8_t prefix)
+{
+
+       switch (addr->af) {
+       case AF_INET:
+               addr->ipaddr.ip4addr = fr_inaddr_mask(&addr->ipaddr.ip4addr, prefix);
+               break;
+
+       case AF_INET6:
+               addr->ipaddr.ip6addr = fr_in6addr_mask(&addr->ipaddr.ip6addr, prefix);
+               break;
+
+       default:
+               return;
+       }
+       addr->prefix = prefix;
+}
+
+static char const hextab[] = "0123456789abcdef";
 
 /** Convert hex strings to binary data
  *
  * @param bin Buffer to write output to.
- * @param hex input string.
  * @param outlen length of output buffer (or length of input string / 2).
+ * @param hex input string.
+ * @param inlen length of the input string
  * @return length of data written to buffer.
  */
-size_t fr_hex2bin(uint8_t *bin, char const *hex, size_t outlen)
+size_t fr_hex2bin(uint8_t *bin, size_t outlen, char const *hex, size_t inlen)
 {
        size_t i;
+       size_t len;
        char *c1, *c2;
 
-       for (i = 0; i < outlen; i++) {
-               if(!(c1 = memchr(hextab, tolower((int) hex[i << 1]), 16)) ||
-                  !(c2 = memchr(hextab, tolower((int) hex[(i << 1) + 1]), 16)))
+       /*
+        *      Smartly truncate output, caller should check number of bytes
+        *      written.
+        */
+       len = inlen >> 1;
+       if (len > outlen) len = outlen;
+
+       for (i = 0; i < len; i++) {
+               if(!(c1 = memchr(hextab, tolower((int) hex[i << 1]), sizeof(hextab))) ||
+                  !(c2 = memchr(hextab, tolower((int) hex[(i << 1) + 1]), sizeof(hextab))))
                        break;
-                bin[i] = ((c1-hextab)<<4) + (c2-hextab);
+               bin[i] = ((c1-hextab)<<4) + (c2-hextab);
        }
 
        return i;
 }
 
-
 /** Convert binary data to a hex string
  *
  * Ascii encoded hex string will not be prefixed with '0x'
@@ -948,7 +1079,26 @@ size_t fr_bin2hex(char *hex, uint8_t const *bin, size_t inlen)
        return inlen * 2;
 }
 
+/** Convert binary data to a hex string
+ *
+ * Ascii encoded hex string will not be prefixed with '0x'
+ *
+ * @param[in] ctx to alloc buffer in.
+ * @param[in] bin input.
+ * @param[in] inlen of bin input.
+ * @return length of data written to buffer.
+ */
+char *fr_abin2hex(TALLOC_CTX *ctx, uint8_t const *bin, size_t inlen)
+{
+       char *buff;
 
+       buff = talloc_array(ctx, char, (inlen << 2));
+       if (!buff) return NULL;
+
+       fr_bin2hex(buff, bin, inlen);
+
+       return buff;
+}
 
 /** Consume the integer (or hex) portion of a value string
  *
@@ -1115,6 +1265,7 @@ int fr_sockaddr2ipaddr(struct sockaddr_storage const *sa, socklen_t salen,
 
                memcpy(&s4, sa, sizeof(s4));
                ipaddr->af = AF_INET;
+               ipaddr->prefix = 32;
                ipaddr->ipaddr.ip4addr = s4.sin_addr;
                if (port) *port = ntohs(s4.sin_port);
 
@@ -1129,6 +1280,7 @@ int fr_sockaddr2ipaddr(struct sockaddr_storage const *sa, socklen_t salen,
 
                memcpy(&s6, sa, sizeof(s6));
                ipaddr->af = AF_INET6;
+               ipaddr->prefix = 128;
                ipaddr->ipaddr.ip6addr = s6.sin6_addr;
                if (port) *port = ntohs(s6.sin6_port);
                ipaddr->scope = s6.sin6_scope_id;