From 80e1d1bff2f75d1643b2f8191c5333d4a11e173a Mon Sep 17 00:00:00 2001 From: Jacob Champion Date: Wed, 5 Jan 2022 15:47:03 -0800 Subject: [PATCH v9 3/3] squash! libpq: allow IP address SANs in server certs Per review, provide a pg_inet_pton() interface for IPv6 and refactor the internals accordingly. IPv4 is not yet implemented. The call sites that just want standard addresses without a CIDR mask have been updated to use the new function. Internally, the IPv6 parser now takes an allow_cidr boolean. I've plumbed that all the way down to getbits() in order to minimize the amount of code touched, at the expense of an unnecessary function call in the failing case. --- src/include/port.h | 1 + src/interfaces/libpq/fe-secure-common.c | 7 +-- src/interfaces/libpq/fe-secure-openssl.c | 2 +- src/port/inet_net_pton.c | 66 ++++++++++++++++++------ 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/include/port.h b/src/include/port.h index 2852e5b58b..fd046f4c24 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -517,6 +517,7 @@ extern char *pg_inet_net_ntop(int af, const void *src, int bits, /* port/inet_net_pton.c */ extern int pg_inet_net_pton(int af, const char *src, void *dst, size_t size); +extern int pg_inet_pton(int af, const char *src, void *dst); /* port/pg_strong_random.c */ extern void pg_strong_random_init(void); diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c index 2c0af62afe..9c408df369 100644 --- a/src/interfaces/libpq/fe-secure-common.c +++ b/src/interfaces/libpq/fe-secure-common.c @@ -211,12 +211,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn, family = PGSQL_AF_INET6; - /* - * pg_inet_net_pton() will accept CIDR masks, which we don't want to - * match, so skip the comparison if the host string contains a slash. - */ - if (!strchr(host, '/') - && pg_inet_net_pton(PGSQL_AF_INET6, host, addr, -1) == 128) + if (pg_inet_pton(PGSQL_AF_INET6, host, addr) == 1) { if (memcmp(ipdata, addr, iplen) == 0) match = 1; diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c index 7656c3a75e..b70bd2eb19 100644 --- a/src/interfaces/libpq/fe-secure-openssl.c +++ b/src/interfaces/libpq/fe-secure-openssl.c @@ -555,7 +555,7 @@ is_ip_address(const char *host) unsigned char dummy6[16]; return inet_aton(host, &dummy4) - || (pg_inet_net_pton(PGSQL_AF_INET6, host, dummy6, -1) == 128); + || (pg_inet_pton(PGSQL_AF_INET6, host, dummy6) == 1); } /* diff --git a/src/port/inet_net_pton.c b/src/port/inet_net_pton.c index 5bae811c6f..ae0d1fd2e3 100644 --- a/src/port/inet_net_pton.c +++ b/src/port/inet_net_pton.c @@ -42,8 +42,8 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m static int inet_net_pton_ipv4(const char *src, u_char *dst); static int inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size); -static int inet_net_pton_ipv6(const char *src, u_char *dst); -static int inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size); +static int inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, + bool allow_cidr); /* @@ -74,9 +74,9 @@ pg_inet_net_pton(int af, const char *src, void *dst, size_t size) inet_net_pton_ipv4(src, dst) : inet_cidr_pton_ipv4(src, dst, size); case PGSQL_AF_INET6: - return size == -1 ? - inet_net_pton_ipv6(src, dst) : - inet_cidr_pton_ipv6(src, dst, size); + return inet_pton_ipv6_internal(src, dst, + (size == -1) ? 16 : size, + true /* allow CIDR */); default: errno = EAFNOSUPPORT; return -1; @@ -352,13 +352,22 @@ emsgsize: } static int -getbits(const char *src, int *bitsp) +getbits(const char *src, int *bitsp, bool allow_cidr) { static const char digits[] = "0123456789"; int n; int val; char ch; + if (!allow_cidr) + { + /* + * If we got here, the top-level call is to pg_inet_pton() and the + * caller supplied a CIDR mask, which isn't allowed. + */ + return 0; + } + val = 0; n = 0; while ((ch = *src++) != '\0') @@ -385,7 +394,7 @@ getbits(const char *src, int *bitsp) } static int -getv4(const char *src, u_char *dst, int *bitsp) +getv4(const char *src, u_char *dst, int *bitsp, bool allow_cidr) { static const char digits[] = "0123456789"; u_char *odst = dst; @@ -416,7 +425,7 @@ getv4(const char *src, u_char *dst, int *bitsp) return 0; *dst++ = val; if (ch == '/') - return getbits(src, bitsp); + return getbits(src, bitsp, allow_cidr); val = 0; n = 0; continue; @@ -431,18 +440,12 @@ getv4(const char *src, u_char *dst, int *bitsp) return 1; } -static int -inet_net_pton_ipv6(const char *src, u_char *dst) -{ - return inet_cidr_pton_ipv6(src, dst, 16); -} - #define NS_IN6ADDRSZ 16 #define NS_INT16SZ 2 #define NS_INADDRSZ 4 static int -inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size) +inet_pton_ipv6_internal(const char *src, u_char *dst, size_t size, bool allow_cidr) { static const char xdigits_l[] = "0123456789abcdef", xdigits_u[] = "0123456789ABCDEF"; @@ -510,13 +513,13 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size) continue; } if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && - getv4(curtok, tp, &bits) > 0) + getv4(curtok, tp, &bits, allow_cidr) > 0) { tp += NS_INADDRSZ; saw_xdigit = 0; break; /* '\0' was seen by inet_pton4(). */ } - if (ch == '/' && getbits(src, &bits) > 0) + if (ch == '/' && getbits(src, &bits, allow_cidr) > 0) break; goto enoent; } @@ -530,6 +533,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size) if (bits == -1) bits = 128; + /* Without a CIDR mask, the above should guarantee a complete address. */ + Assert(allow_cidr || bits == 128); + endp = tmp + 16; if (colonp != NULL) @@ -568,3 +574,29 @@ emsgsize: errno = EMSGSIZE; return -1; } + +/* + * Converts a network address in presentation format to network format. The main + * difference between this and pg_inet_net_pton() above is that CIDR notation is + * not allowed. + * + * The memory pointed to by dst depends on the address family: + * + * PGSQL_AF_INET: not implemented yet (returns -1 with EAFNOSUPPORT) + * PGSQL_AF_INET6: *dst must be a u_char[16] + * + * Over and above the standard inet_pton() functionality, we also set errno on + * parse failure, with the same meanings as with pg_inet_net_pton(). + */ +int +pg_inet_pton(int af, const char *src, void *dst) +{ + if (af != PGSQL_AF_INET6) + { + /* Currently only IPv6 is implemented. */ + errno = EAFNOSUPPORT; + return -1; + } + + return (inet_pton_ipv6_internal(src, dst, 16, false /* no CIDR */) == 128); +} -- 2.25.1