diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 2ad7a6a29fc3fd17d328858268d58987096691bd..481957e4ee17b09fb06c329ff027b883cc62e3af 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/client-auth.sgml,v 1.56 2003/08/31 17:32:18 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/client-auth.sgml,v 1.57 2003/09/05 20:31:35 tgl Exp $
 -->
 
 <chapter id="client-authentication">
@@ -199,13 +199,17 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
 <programlisting>
 (<replaceable>actual-IP-address</replaceable> xor <replaceable>IP-address-field</replaceable>) and <replaceable>IP-mask-field</replaceable>
 </programlisting>
-       must be zero for the record to match.  (Of course IP addresses
-       can be spoofed but this consideration is beyond the scope of
-       <productname>PostgreSQL</productname>.) If you machine supports
-       IPv6, the default <filename>pg_hba.conf</> file will have an
-       IPv6 entry for <literal>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.
+      </para>
+
+      <para>
+       An IP address given in IPv4 format will match IPv6 connections that
+       have the corresponding address, for example <literal>127.0.0.1</>
+       will match the IPv6 address <literal>::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.
       </para>
 
       <para>
@@ -219,9 +223,10 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
      <term><replaceable>CIDR-mask</replaceable></term>
      <listitem>
       <para>
-        This is an integer specifying the number of significant bits 
-        to set in the mask, and is an alternative to using the 
-        <replaceable>IP-mask</replaceable> notation. The number must
+        This field may be used as an alternative to the 
+        <replaceable>IP-mask</replaceable> 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 4a677585625cf2bdf3b4e748129cc9063745c508..2e2126362118176b724cf72b1c58748f056bcab7 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 8ae6c8f7848bc5a07c17095d3af9eb4e3567c793..6ab5f57fd785494555e68ef24116692a52796905 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 <nkukard@lbsd.net>, Linux Based Systems Design
@@ -34,7 +34,8 @@
 #endif
 #include <arpa/inet.h>
 #include <sys/file.h>
-#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)
+
+#ifdef HAVE_IPV6
+
+/*
+ * 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)
 {
-	if (((addr->sin_addr.s_addr ^ netaddr->sin_addr.s_addr) &
-		 netmask->sin_addr.s_addr) == 0)
-		return 1;
-	else
-		return 0;
-}
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+	uint32		s_addr;
 
+	memcpy(&addr4, addr, sizeof(addr4));
+	s_addr = ntohl(addr4.sin_addr.s_addr);
 
-#ifdef HAVE_IPV6
-static int
-rangeSockAddrAF_INET6(const struct sockaddr_in6 * addr,
-					  const struct sockaddr_in6 * netaddr,
-					  const struct sockaddr_in6 * netmask)
+	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));
+}
+
+/*
+ * 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;
 
-	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;
+
+	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
+#endif /* HAVE_IPV6 */
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index e5dba788c66733212a3d17f12d94a2a16bcc633f..53c65cc81ae9743930e6d30f207f3a0d31159790 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 c60030ccf50f951a2a4ea517fad4755271f06ad6..9858faaddc9888b8cb1fbc3930b807c37952f83b 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