From 3c9bb8886df7d56aaeb619abc246462fa809d946 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 5 Sep 2003 20:31:36 +0000 Subject: [PATCH] Allow IPv4-format entries in pg_hba.conf to match IPv6 connections that have IPv4-embedded-in-IPv6 addresses. Per idea of Andreas Pflug. --- doc/src/sgml/client-auth.sgml | 27 +++--- src/backend/libpq/hba.c | 31 +++++-- src/backend/libpq/ip.c | 133 +++++++++++++++++++++------ src/backend/libpq/pg_hba.conf.sample | 3 +- src/include/libpq/ip.h | 7 +- 5 files changed, 153 insertions(+), 48 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 2ad7a6a29f..481957e4ee 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,5 +1,5 @@ @@ -199,13 +199,17 @@ hostnossl database user (actual-IP-address xor IP-address-field) and IP-mask-field - must be zero for the record to match. (Of course IP addresses - can be spoofed but this consideration is beyond the scope of - PostgreSQL.) If you machine supports - IPv6, the default pg_hba.conf file will have an - IPv6 entry for localhost. You can add your own IPv6 - entries to the file. IPv6 entries are used only for IPv6 - connections. + must be zero for the record to match. + + + + An IP address given in IPv4 format will match IPv6 connections that + have the corresponding address, for example 127.0.0.1 + will match the IPv6 address ::ffff:127.0.0.1. An entry + given in IPv6 format will match only IPv6 connections, even if the + represented address is in the IPv4-in-IPv6 range. Note that entries + in IPv6 format will be rejected if the system's C library does not have + support for IPv6 addresses. @@ -219,9 +223,10 @@ hostnossl database user CIDR-mask - This is an integer specifying the number of significant bits - to set in the mask, and is an alternative to using the - IP-mask notation. The number must + This field may be used as an alternative to the + IP-mask notation. It is an + integer specifying the number of high-order bits + to set in the mask. The number must be between 0 and 32 (in the case of an IPv4 address) or 128 (in the case of an IPv6 address) inclusive. 0 will match any address, while 32/128 will match only the exact host specified. diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 4a67758562..2e21263621 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.112 2003/09/05 03:57:13 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.113 2003/09/05 20:31:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -673,13 +673,6 @@ parse_hba(List *line, hbaPort *port, bool *found_p, bool *error_p) if (cidr_slash) *cidr_slash = '/'; - if (file_ip_addr->ai_family != port->raddr.addr.ss_family) - { - /* Wrong address family. */ - freeaddrinfo_all(hints.ai_family, file_ip_addr); - return; - } - /* Get the netmask */ if (cidr_slash) { @@ -705,6 +698,28 @@ parse_hba(List *line, hbaPort *port, bool *found_p, bool *error_p) goto hba_syntax; } + if (file_ip_addr->ai_family != port->raddr.addr.ss_family) + { + /* + * Wrong address family. We allow only one case: if the + * file has IPv4 and the port is IPv6, promote the file + * address to IPv6 and try to match that way. + */ +#ifdef HAVE_IPV6 + if (file_ip_addr->ai_family == AF_INET && + port->raddr.addr.ss_family == AF_INET6) + { + promote_v4_to_v6_addr((struct sockaddr_storage *) file_ip_addr->ai_addr); + promote_v4_to_v6_mask(mask); + } + else +#endif /* HAVE_IPV6 */ + { + freeaddrinfo_all(hints.ai_family, file_ip_addr); + return; + } + } + /* Read the rest of the line. */ line = lnext(line); if (!line) diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c index 8ae6c8f784..6ab5f57fd7 100644 --- a/src/backend/libpq/ip.c +++ b/src/backend/libpq/ip.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/ip.c,v 1.19 2003/08/04 02:39:59 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/ip.c,v 1.20 2003/09/05 20:31:36 tgl Exp $ * * This file and the IPV6 implementation were initially provided by * Nigel Kukard , Linux Based Systems Design @@ -34,7 +34,8 @@ #endif #include #include -#endif + +#endif /* !defined(_MSC_VER) && !defined(__BORLANDC__) */ #include "libpq/ip.h" @@ -265,9 +266,16 @@ getnameinfo_unix(const struct sockaddr_un * sa, int salen, return 0; } + #endif /* HAVE_UNIX_SOCKETS */ +/* + * rangeSockAddr - is addr within the subnet specified by netaddr/netmask ? + * + * Note: caller must already have verified that all three addresses are + * in the same address family; and AF_UNIX addresses are not supported. + */ int rangeSockAddr(const struct sockaddr_storage * addr, const struct sockaddr_storage * netaddr, @@ -287,6 +295,39 @@ rangeSockAddr(const struct sockaddr_storage * addr, return 0; } +static int +rangeSockAddrAF_INET(const struct sockaddr_in * addr, + const struct sockaddr_in * netaddr, + const struct sockaddr_in * netmask) +{ + if (((addr->sin_addr.s_addr ^ netaddr->sin_addr.s_addr) & + netmask->sin_addr.s_addr) == 0) + return 1; + else + return 0; +} + + +#ifdef HAVE_IPV6 +static int +rangeSockAddrAF_INET6(const struct sockaddr_in6 * addr, + const struct sockaddr_in6 * netaddr, + const struct sockaddr_in6 * netmask) +{ + int i; + + for (i = 0; i < 16; i++) + { + if (((addr->sin6_addr.s6_addr[i] ^ netaddr->sin6_addr.s6_addr[i]) & + netmask->sin6_addr.s6_addr[i]) != 0) + return 0; + } + + return 1; +} + +#endif + /* * SockAddr_cidr_mask - make a network mask of the appropriate family * and required number of significant bits @@ -358,34 +399,74 @@ SockAddr_cidr_mask(struct sockaddr_storage ** mask, char *numbits, int family) return 0; } -static int -rangeSockAddrAF_INET(const struct sockaddr_in * addr, const struct sockaddr_in * netaddr, - const struct sockaddr_in * netmask) -{ - if (((addr->sin_addr.s_addr ^ netaddr->sin_addr.s_addr) & - netmask->sin_addr.s_addr) == 0) - return 1; - else - return 0; -} - #ifdef HAVE_IPV6 -static int -rangeSockAddrAF_INET6(const struct sockaddr_in6 * addr, - const struct sockaddr_in6 * netaddr, - const struct sockaddr_in6 * netmask) + +/* + * promote_v4_to_v6_addr --- convert an AF_INET addr to AF_INET6, using + * the standard convention for IPv4 addresses mapped into IPv6 world + * + * The passed addr is modified in place. Note that we only worry about + * setting the fields that rangeSockAddr will look at. + */ +void +promote_v4_to_v6_addr(struct sockaddr_storage * addr) { - int i; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + uint32 s_addr; - for (i = 0; i < 16; i++) - { - if (((addr->sin6_addr.s6_addr[i] ^ netaddr->sin6_addr.s6_addr[i]) & - netmask->sin6_addr.s6_addr[i]) != 0) - return 0; - } + memcpy(&addr4, addr, sizeof(addr4)); + s_addr = ntohl(addr4.sin_addr.s_addr); - return 1; + memset(&addr6, 0, sizeof(addr6)); + + addr6.sin6_family = AF_INET6; + + addr6.sin6_addr.s6_addr[10] = 0xff; + addr6.sin6_addr.s6_addr[11] = 0xff; + addr6.sin6_addr.s6_addr[12] = (s_addr >> 24) & 0xFF; + addr6.sin6_addr.s6_addr[13] = (s_addr >> 16) & 0xFF; + addr6.sin6_addr.s6_addr[14] = (s_addr >> 8) & 0xFF; + addr6.sin6_addr.s6_addr[15] = (s_addr) & 0xFF; + + memcpy(addr, &addr6, sizeof(addr6)); } -#endif +/* + * promote_v4_to_v6_mask --- convert an AF_INET netmask to AF_INET6, using + * the standard convention for IPv4 addresses mapped into IPv6 world + * + * This must be different from promote_v4_to_v6_addr because we want to + * set the high-order bits to 1's not 0's. + * + * The passed addr is modified in place. Note that we only worry about + * setting the fields that rangeSockAddr will look at. + */ +void +promote_v4_to_v6_mask(struct sockaddr_storage * addr) +{ + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + uint32 s_addr; + int i; + + memcpy(&addr4, addr, sizeof(addr4)); + s_addr = ntohl(addr4.sin_addr.s_addr); + + memset(&addr6, 0, sizeof(addr6)); + + addr6.sin6_family = AF_INET6; + + for (i = 0; i < 12; i++) + addr6.sin6_addr.s6_addr[i] = 0xff; + + addr6.sin6_addr.s6_addr[12] = (s_addr >> 24) & 0xFF; + addr6.sin6_addr.s6_addr[13] = (s_addr >> 16) & 0xFF; + addr6.sin6_addr.s6_addr[14] = (s_addr >> 8) & 0xFF; + addr6.sin6_addr.s6_addr[15] = (s_addr) & 0xFF; + + memcpy(addr, &addr6, sizeof(addr6)); +} + +#endif /* HAVE_IPV6 */ diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample index e5dba788c6..53c65cc81a 100644 --- a/src/backend/libpq/pg_hba.conf.sample +++ b/src/backend/libpq/pg_hba.conf.sample @@ -53,6 +53,5 @@ local all all trust host all all 127.0.0.1 255.255.255.255 trust -# uncomment these to support IPv6 localhost connections +# uncomment this to support IPv6 loopback connections # host all all ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff trust -# host all all ::ffff:127.0.0.1/128 trust diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h index c60030ccf5..9858faaddc 100644 --- a/src/include/libpq/ip.h +++ b/src/include/libpq/ip.h @@ -5,7 +5,7 @@ * * Copyright (c) 2003, PostgreSQL Global Development Group * - * $Id: ip.h,v 1.10 2003/08/04 00:43:31 momjian Exp $ + * $Id: ip.h,v 1.11 2003/09/05 20:31:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,6 +33,11 @@ extern int rangeSockAddr(const struct sockaddr_storage * addr, extern int SockAddr_cidr_mask(struct sockaddr_storage ** mask, char *numbits, int family); +#ifdef HAVE_IPV6 +extern void promote_v4_to_v6_addr(struct sockaddr_storage * addr); +extern void promote_v4_to_v6_mask(struct sockaddr_storage * addr); +#endif + #ifdef HAVE_UNIX_SOCKETS #define IS_AF_UNIX(fam) ((fam) == AF_UNIX) #else