Here is a patch for host name support in pg_hba.conf.  I have reviewed
various past threads about this, and there appeared to have been a 50/50
split of for and against reverse lookup.  I went with the reverse
lookup, because

0) I like it.

1) It is more secure.

2) It allows extending it to wildcards in the future.

3) Apache (Allow from) does it that way.

To clarify how it works:  The client's IP address (known from the
kernel) is reverse looked up, which results in a host name.  That host
name is compared with the line in pg_hba.conf.  If it matches, a forward
lookup is performed on the host name to check if any of the resulting IP
addresses match the client's IP address.  If yes, the line is considered
to match and the authentication method is selected.

Anyway, assuming we will go with this, you will also notice that in the
patch I changed the default pg_hba.conf to match against "localhost"
instead of numeric addresses.  Initially thought of as a temporary
change for testing this patch, I think this might actually have some
permanent value because it saves you from having to change the IPv4 and
IPv6 lines in tandem most of the times, which is a moderately common
mistake.  We already rely on localhost being (forward) resolvable for
the stats collector.

Something to think about: Maybe we need a quoting mechanism in case
someone names their hosts "samenet".
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index e8995f9..808c468 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -101,9 +101,9 @@
    A record can have one of the seven formats
 <synopsis>
 local      <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
-host       <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>CIDR-address</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
-hostssl    <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>CIDR-address</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
-hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>CIDR-address</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
+host       <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>address</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
+hostssl    <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>address</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
+hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>address</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
 host       <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>IP-address</replaceable>  <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
 hostssl    <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>IP-address</replaceable>  <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
 hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>IP-address</replaceable>  <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
@@ -218,12 +218,17 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
     </varlistentry>
 
     <varlistentry>
-     <term><replaceable>CIDR-address</replaceable></term>
+     <term><replaceable>address</replaceable></term>
      <listitem>
       <para>
        Specifies the client machine IP address range that this record
-       matches. This field contains an IP address in standard dotted decimal
-       notation and a <acronym>CIDR</> mask length. (IP addresses can only be
+       matches.  This field can contain either a host name, an IP
+       address range, one of the special key words mentioned below.
+      </para>
+
+      <para>
+       An IP address is specified in standard dotted decimal
+       notation with a <acronym>CIDR</> mask length. (IP addresses can only be
        specified numerically, not as domain or host names.)  The mask
        length indicates the number of high-order bits of the client
        IP address that must match.  Bits to the right of this must
@@ -233,14 +238,7 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
       </para>
 
       <para>
-       Instead of a <replaceable>CIDR-address</replaceable>, you can write
-       <literal>samehost</literal> to match any of the server's own IP
-       addresses, or <literal>samenet</literal> to match any address in any
-       subnet that the server is directly connected to.
-      </para>
-
-      <para>
-       Typical examples of a <replaceable>CIDR-address</replaceable> are
+       Typical examples of an IP address range specified this way are
        <literal>172.20.143.89/32</literal> for a single host, or
        <literal>172.20.143.0/24</literal> for a small network, or
        <literal>10.6.0.0/16</literal> for a larger one.
@@ -260,6 +258,24 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
       </para>
 
       <para>
+       You can also write
+       <literal>samehost</literal> to match any of the server's own IP
+       addresses, or <literal>samenet</literal> to match any address in any
+       subnet that the server is directly connected to.
+      </para>
+
+      <para>
+       If a host name is specified (anything that is not an IP address
+       or a special key word is processed as a potential host name), a
+       reverse DNS lookup is performed on the client's IP address,
+       then a forward DNS lookup on the resulting name to check if it
+       matches the original IP address (that is, at least one of the
+       potentially many IP addresses matches the original one), and
+       the name found in the reverse lookup is compared with the
+       specified host name.
+      </para>
+
+      <para>
        This field only applies to <literal>host</literal>,
        <literal>hostssl</literal>, and <literal>hostnossl</> records.
       </para>
@@ -511,12 +527,12 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
 # any database user name using Unix-domain sockets (the default for local
 # connections).
 #
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 local   all             all                                     trust
 
 # The same using local loopback TCP/IP connections.
 #
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    all             all             127.0.0.1/32            trust
 
 # The same as the previous line, but using a separate netmask column
@@ -524,17 +540,27 @@ host    all             all             127.0.0.1/32            trust
 # TYPE  DATABASE        USER            IP-ADDRESS      IP-MASK             METHOD
 host    all             all             127.0.0.1       255.255.255.255     trust
 
+# The same over IPv6.
+#
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
+host    all             all             ::1/128                 trust
+
+# The same using a host name (would typically cover both IPv4 and IPv6).
+#
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
+host    all             all             localhost               trust
+
 # Allow any user from any host with IP address 192.168.93.x to connect
 # to database "postgres" as the same user name that ident reports for
 # the connection (typically the operating system user name).
 #
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    postgres        all             192.168.93.0/24         ident
 
 # Allow any user from host 192.168.12.10 to connect to database
 # "postgres" if the user's password is correctly supplied.
 #
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    postgres        all             192.168.12.10/32        md5
 
 # In the absence of preceding "host" lines, these two lines will
@@ -543,7 +569,7 @@ host    postgres        all             192.168.12.10/32        md5
 # on the Internet.  The zero mask causes no bits of the host IP
 # address to be considered, so it matches any host.
 #
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    all             all             192.168.54.1/32         reject
 host    all             all             0.0.0.0/0               krb5
 
@@ -553,7 +579,7 @@ host    all             all             0.0.0.0/0               krb5
 # connection is allowed if there is an entry in pg_ident.conf for map
 # "omicron" that says "bryanh" is allowed to connect as "guest1".
 #
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    all             all             192.168.0.0/16          ident map=omicron
 
 # If these are the only three lines for local connections, they will
@@ -563,7 +589,7 @@ host    all             all             192.168.0.0/16          ident map=omicro
 # $PGDATA/admins contains a list of names of administrators.  Passwords
 # are required in all cases.
 #
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 local   sameuser        all                                     md5
 local   all             @admins                                 md5
 local   all             +support                                md5
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index aec12b0..b2ff30d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -540,6 +540,99 @@ check_db(const char *dbname, const char *role, Oid roleid, char *param_str)
 	return false;
 }
 
+static bool
+ipv4eq(struct sockaddr_in *a, struct sockaddr_in *b)
+{
+	return (a->sin_addr.s_addr == b->sin_addr.s_addr);
+}
+
+static bool
+ipv6eq(struct sockaddr_in6 *a, struct sockaddr_in6 *b)
+{
+	int i;
+
+	for (i = 0; i < 16; i++)
+		if (a->sin6_addr.s6_addr[i] != b->sin6_addr.s6_addr[i])
+			return false;
+
+	return true;
+}
+
+/*
+ * Check to see if a connecting IP matches a given host name.
+ */
+static bool
+check_hostname(hbaPort *port, const char *hostname)
+{
+	struct addrinfo *gai_result, *gai;
+	int			ret;
+	bool		found;
+
+	/* Lookup remote host name if not already done */
+	if (!port->remote_hostname)
+	{
+		char		remote_hostname[NI_MAXHOST];
+
+		if (pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
+							   remote_hostname, sizeof(remote_hostname),
+							   NULL, 0,
+							   0))
+			return false;
+
+		port->remote_hostname = strdup(remote_hostname);
+	}
+
+	if (strcmp(port->remote_hostname, hostname) != 0)
+		return false;
+
+	/* Lookup IP from host name and check against original IP */
+
+	ret = getaddrinfo(port->remote_hostname, NULL, NULL, &gai_result);
+	if (ret != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("getaddrinfo failed on \"%s\": %s",
+						port->remote_hostname, gai_strerror(ret))));
+
+	found = false;
+	for (gai = gai_result; gai; gai = gai->ai_next)
+	{
+		char		hostinfo[NI_MAXHOST];
+
+		getnameinfo(gai->ai_addr, gai->ai_addrlen,
+					hostinfo, sizeof(hostinfo),
+					NULL, 0,
+					NI_NUMERICHOST);
+
+		if (gai->ai_addr->sa_family == port->raddr.addr.ss_family)
+		{
+			if (gai->ai_addr->sa_family == AF_INET)
+			{
+				if (ipv4eq((struct sockaddr_in *) gai->ai_addr,
+						   (struct sockaddr_in *) &port->raddr.addr))
+				{
+					found = true;
+					break;
+				}
+			}
+			else if (gai->ai_addr->sa_family == AF_INET6)
+			{
+				if (ipv6eq((struct sockaddr_in6 *) gai->ai_addr,
+						   (struct sockaddr_in6 *) &port->raddr.addr))
+				{
+					found = true;
+					break;
+				}
+			}
+		}
+	}
+
+	if (gai_result)
+		freeaddrinfo(gai_result);
+
+	return found;
+}
+
 /*
  * Check to see if a connecting IP matches the given address and netmask.
  */
@@ -816,7 +909,12 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 			hints.ai_next = NULL;
 
 			ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
-			if (ret || !gai_result)
+			if (ret == 0 && gai_result)
+				memcpy(&parsedline->addr, gai_result->ai_addr,
+					   gai_result->ai_addrlen);
+			else if (ret == EAI_NONAME)
+				parsedline->hostname = token;
+			else
 			{
 				ereport(LOG,
 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
@@ -830,12 +928,10 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 				return false;
 			}
 
-			memcpy(&parsedline->addr, gai_result->ai_addr,
-				   gai_result->ai_addrlen);
 			pg_freeaddrinfo_all(hints.ai_family, gai_result);
 
 			/* Get the netmask */
-			if (cidr_slash)
+			if (!parsedline->hostname && cidr_slash)
 			{
 				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
 										  parsedline->addr.ss_family) < 0)
@@ -852,7 +948,7 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 				}
 				pfree(token);
 			}
-			else
+			else if (!parsedline->hostname)
 			{
 				/* Read the mask field. */
 				pfree(token);
@@ -1369,10 +1465,19 @@ check_hba(hbaPort *port)
 			switch (hba->ip_cmp_method)
 			{
 				case ipCmpMask:
-					if (!check_ip(&port->raddr,
-								  (struct sockaddr *) & hba->addr,
-								  (struct sockaddr *) & hba->mask))
-						continue;
+					if (hba->hostname)
+					{
+						if (!check_hostname(port,
+											hba->hostname))
+							continue;
+					}
+					else
+					{
+						if (!check_ip(&port->raddr,
+									  (struct sockaddr *) & hba->addr,
+									  (struct sockaddr *) & hba->mask))
+							continue;
+					}
 					break;
 				case ipCmpSameHost:
 				case ipCmpSameNet:
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index e1017cf..65842ea 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -10,9 +10,9 @@
 # databases they can access.  Records take one of these forms:
 #
 # local      DATABASE  USER  METHOD  [OPTIONS]
-# host       DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
-# hostssl    DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
-# hostnossl  DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
+# host       DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
+# hostssl    DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
+# hostnossl  DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
 #
 # (The uppercase items must be replaced by actual values.)
 #
@@ -29,7 +29,8 @@
 # you can also write a file name prefixed with "@" to include names
 # from a separate file.
 #
-# CIDR-ADDRESS specifies the set of hosts the record matches.  It is
+# ADDRESS specifies the set of hosts the record matches.  It can be a
+# host name, or it is
 # made up of an IP address and a CIDR mask that is an integer (between
 # 0 and 32 (IPv4) or 128 (IPv6) inclusive) that specifies the number
 # of significant bits in the mask.  Alternatively, you can write an IP
@@ -70,11 +71,9 @@
 
 @authcomment@
 
-# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
 
 @remove-line-for-nolo...@# "local" is for Unix domain socket connections only
 @remove-line-for-nolo...@local   all             all                                     @authmethod@
-# IPv4 local connections:
-host    all             all             127.0.0.1/32            @authmethod@
-# IPv6 local connections:
-host    all             all             ::1/128                 @authmethod@
+# IPv4/6 local connections:
+host    all             all             localhost               @authmethod@
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index db61569..83182ac 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -3407,6 +3407,8 @@ BackendInitialize(Port *port)
 	 */
 	port->remote_host = strdup(remote_host);
 	port->remote_port = strdup(remote_port);
+	if (log_hostname)
+		port->remote_hostname = port->remote_host;
 
 	/*
 	 * Ready to begin client interaction.  We will give up and exit(1) after a
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index da3a2a3..97686ab 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -56,6 +56,7 @@ typedef struct
 	struct sockaddr_storage addr;
 	struct sockaddr_storage mask;
 	IPCompareMethod ip_cmp_method;
+	char	   *hostname;
 	UserAuth	auth_method;
 
 	char	   *usermap;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a80f74..e364ff0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -109,6 +109,7 @@ typedef struct Port
 	SockAddr	laddr;			/* local addr (postmaster) */
 	SockAddr	raddr;			/* remote addr (client) */
 	char	   *remote_host;	/* name (or ip addr) of remote host */
+	char	   *remote_hostname; /* name (not ip addr) of remote host, if available */
 	char	   *remote_port;	/* text rep of remote port */
 	CAC_state	canAcceptConnections;	/* postmaster connection status */
 
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to