diff --git a/src/lib/util/inet.c b/src/lib/util/inet.c index f65d4d501506..433d4cc3f7d4 100644 --- a/src/lib/util/inet.c +++ b/src/lib/util/inet.c @@ -631,56 +631,141 @@ int fr_inet_pton6(fr_ipaddr_t *out, char const *value, ssize_t inlen, bool resol int fr_inet_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, int af, bool resolve, bool mask) { size_t len, i; + bool hostname = true; + bool ipv4 = true; + bool ipv6 = true; len = (inlen >= 0) ? (size_t)inlen : strlen(value); - for (i = 0; i < len; i++) switch (value[i]) { - /* - * ':' is illegal in domain names and IPv4 addresses. - * Must be v6 and cannot be a domain. - */ - case ':': - return fr_inet_pton6(out, value, inlen, false, false, mask); + + for (i = 0; i < len; i++) { + /* + * These are valid for IPv4, IPv6, and host names. + */ + if ((value[i] >= '0') && (value[i] <= '9')) { + continue; + } + + /* + * These are invalid for IPv4, but OK for IPv6 + * and host names. + */ + if ((value[i] >= 'a') && (value[i] <= 'f')) { + ipv4 = false; + continue; + } + + /* + * These are invalid for IPv4, but OK for IPv6 + * and host names. + */ + if ((value[i] >= 'A') && (value[i] <= 'F')) { + ipv4 = false; + continue; + } + + /* + * This is only valid for IPv6 addresses. + */ + if (value[i] == ':') { + ipv4 = false; + hostname = false; + continue; + } + + /* + * Valid for IPv4 and host names, not for IPv6. + */ + if (value[i] == '.') { + ipv6 = false; + continue; + } + + /* + * Netmasks are allowed by us, and MUST come at + * the end of the address. + */ + if (value[i] == '/') { + break; + } + + /* + * Any characters other than what are checked for + * above can't be IPv4 or IPv6 addresses. + */ + ipv4 = false; + ipv6 = false; + } /* - * Chars which don't really tell us anything + * It's not an IPv4 or IPv6 address. It MUST be a host + * name. */ - case '.': - case '/': - continue; - - default: + if (!ipv4 && !ipv6) { /* - * Outside the range of IPv4 chars, must be a domain - * Use A record in preference to AAAA record. + * Not an IPv4 or IPv6 address, and we weren't + * asked to do DNS resolution, we can't do it. */ - if ((value[i] < '0') || (value[i] > '9')) { - if (!resolve) { - fr_strerror_printf("Not IPv4/6 address, and asked not to resolve"); - return -1; - } - switch (af) { - case AF_UNSPEC: - return fr_inet_pton4(out, value, inlen, resolve, true, mask); + if (!resolve) { + fr_strerror_printf("Not IPv4/6 address, and asked not to resolve"); + return -1; + } - case AF_INET: - return fr_inet_pton4(out, value, inlen, resolve, false, mask); + /* + * It's not a hostname, either, so bail out + * early. + */ + if (!hostname) { + fr_strerror_printf("Invalid address"); + return -1; + } - case AF_INET6: - return fr_inet_pton6(out, value, inlen, resolve, false, mask); + /* + * Fall through to resolving the address, using + * whatever address family they prefer. If they + * don't specify an address family, force IPv4. + */ + if (af == AF_UNSPEC) af = AF_INET; + } - default: - fr_strerror_printf("Invalid address family %i", af); - return -1; - } + /* + * The name has a ':' in it. Therefore it must be an + * IPv6 address. Error out if the caller specified IPv4. + * Otherwise, force IPv6. + */ + if (ipv6 && !hostname) { + if (af == AF_INET) { + fr_strerror_printf("Invalid address"); + return -1; } + + af = AF_INET6; + } + + fprintf(stderr, "AF %d %s\n", af, value); + + /* + * Use whatever the caller specified, OR what we + * insinuated above from looking at the name string. + */ + switch (af) { + case AF_UNSPEC: + return fr_inet_pton4(out, value, inlen, resolve, true, mask); + + case AF_INET: + return fr_inet_pton4(out, value, inlen, resolve, false, mask); + + case AF_INET6: + return fr_inet_pton6(out, value, inlen, resolve, false, mask); + + default: break; } - /* - * All chars were in the IPv4 set [0-9/.], must be an IPv4 - * address. - */ - return fr_inet_pton4(out, value, inlen, false, false, mask); + /* + * No idea what it is... + */ + fr_strerror_printf("Invalid address family %i", af); + return -1; } /** Parses IPv4/6 address + port, to fr_ipaddr_t and integer (port)