A record is made
up of a number of fields which are separated by spaces and/or tabs.
Fields can contain white space if the field value is quoted.
- Quoting one of the keywords in a database or user name field (e.g.,
+ Quoting one of the keywords in a database, user, or address field (e.g.,
<literal>all</> or <literal>replication</>) makes the word lose its special
- character, and just match a database or user with that name.
+ character, and just match a database, user, or host with that name.
</para>
<para>
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>
</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
- specified numerically, not as domain or host names.) The mask
+ Specifies the client machine addresses that this record
+ matches. This field can contain either a host name, an IP
+ address range, or 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. 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
be zero in the given IP address.
</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.
support for IPv6 addresses.
</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),
+ that name is compared with the result of a reverse name
+ resolution of the client's IP address (e.g., reverse DNS
+ lookup, if DNS is used). Host name comparisons are case
+ insensitive. If there is a match, then a forward name
+ resolution (e.g., forward DNS lookup) is performed on the host
+ name to check whether any of the addresses it resolves to are
+ equal to the client's IP address. If both directions match,
+ then the entry is considered to match. (The host name that is
+ used in <filename>pg_hba.conf</filename> should be the one that
+ address-to-name resolution of the client's IP address returns,
+ otherwise the line won't be matched. Some host name databases
+ allow associating an IP address with multiple host names, but
+ the operating system will only return one host name when asked
+ to resolve an IP address.)
+ </para>
+
+ <para>
+ When host names are specified
+ in <filename>pg_hba.conf</filename>, you should make sure that
+ name resolution is reasonably fast. It can be of advantage to
+ set up a local name resolution cache such
+ as <command>nscd</command>. Also, you may wish to enable the
+ configuration parameter <varname>log_hostname</varname> to see
+ the client's host name instead of the IP address in the log.
+ </para>
+
+ <sidebar>
+ <para>
+ Occasionally, users have wondered why host names are handled
+ in this seemingly complicated way with two name resolutions
+ and requiring reverse lookup of IP addresses, which is
+ sometimes not set up or points to some undesirable host name.
+ It is primarily for efficiency: A connection attempt requires
+ two resolver lookups of the current client's address. If
+ there is resolver problem with that address, it becomes only
+ that client's problem. A hypothetical alternative
+ implementation which only does forward lookups would have to
+ resolve every host name mentioned in
+ <filename>pg_hba.conf</filename> at every connection attempt.
+ That would already be slow by itself. And if there is a
+ resolver problem with one of the host names, it becomes
+ everyone's problem.
+ </para>
+
+ <para>
+ Note that this behavior is consistent with other popular
+ implementations of host name-based access control, such as the
+ Apache HTTP Server and TCP Wrappers.
+ </para>
+ </sidebar>
+
<para>
This field only applies to <literal>host</literal>,
<literal>hostssl</literal>, and <literal>hostnossl</> records.
# 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
# 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
# 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
# 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
# $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
* whichever comes first. If no more tokens on line, position the file to the
* beginning of the next line or EOF, whichever comes first.
*
- * Handle comments. Treat unquoted keywords that might be role names or
- * database names specially, by appending a newline to them. Also, when
+ * Handle comments. Treat unquoted keywords that might be role, database, or
+ * host names specially, by appending a newline to them. Also, when
* a token is terminated by a comma, the comma is included in the returned
* token.
*/
if (!saw_quote &&
(strcmp(start_buf, "all") == 0 ||
+ strcmp(start_buf, "samehost") == 0 ||
+ strcmp(start_buf, "samenet") == 0 ||
strcmp(start_buf, "sameuser") == 0 ||
strcmp(start_buf, "samegroup") == 0 ||
strcmp(start_buf, "samerole") == 0 ||
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 = pstrdup(remote_hostname);
+ }
+
+ if (pg_strcasecmp(port->remote_hostname, hostname) != 0)
+ return false;
+
+ /* Lookup IP from host name and check against original IP */
+
+ if (port->remote_hostname_resolv == +1)
+ return true;
+ if (port->remote_hostname_resolv == -1)
+ return false;
+
+ ret = getaddrinfo(port->remote_hostname, NULL, NULL, &gai_result);
+ if (ret != 0)
+ ereport(ERROR,
+ (errmsg("could not translate host name \"%s\" to address: %s",
+ port->remote_hostname, gai_strerror(ret))));
+
+ found = false;
+ for (gai = gai_result; gai; gai = gai->ai_next)
+ {
+ 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);
+
+ if (!found)
+ elog(DEBUG2, "pg_hba.conf host name \"%s\" rejected because address resolution did not return a match with IP address of client",
+ hostname);
+
+ port->remote_hostname_resolv = found ? +1 : -1;
+
+ return found;
+}
+
/*
* Check to see if a connecting IP matches the given address and netmask.
*/
token = lfirst(line_item);
/* Is it equal to 'samehost' or 'samenet'? */
- if (strcmp(token, "samehost") == 0)
+ if (strcmp(token, "samehost\n") == 0)
{
/* Any IP on this host is allowed to connect */
parsedline->ip_cmp_method = ipCmpSameHost;
}
- else if (strcmp(token, "samenet") == 0)
+ else if (strcmp(token, "samenet\n") == 0)
{
/* Any IP on the host's subnets is allowed to connect */
parsedline->ip_cmp_method = ipCmpSameNet;
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),
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 = '/'; /* restore token for message */
+ ereport(LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
+ token),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ pfree(token);
+ return false;
+ }
+
if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
parsedline->addr.ss_family) < 0)
{
}
pfree(token);
}
- else
+ else if (!parsedline->hostname)
{
/* Read the mask field. */
pfree(token);
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:
# 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.)
#
# 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
-# 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
-# address and netmask in separate columns to specify the set of hosts.
-# Instead of a CIDR-address, you can write "samehost" to match any of
-# the server's own IP addresses, or "samenet" to match any address in
-# any subnet that the server is directly connected to.
+# 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 address and netmask in separate
+# columns to specify the set of hosts. Instead of a CIDR-address, you
+# can write "samehost" to match any of the server's own IP addresses,
+# or "samenet" to match any address in any subnet that the server is
+# directly connected to.
#
# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
# "krb5", "ident", "pam", "ldap", "radius" or "cert". Note that
@authcomment@
-# TYPE DATABASE USER CIDR-ADDRESS METHOD
+# TYPE DATABASE USER ADDRESS METHOD
@remove-line-for-nolocal@# "local" is for Unix domain socket connections only
@remove-line-for-nolocal@local all all @authmethod@
*/
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
struct sockaddr_storage addr;
struct sockaddr_storage mask;
IPCompareMethod ip_cmp_method;
+ char *hostname;
UserAuth auth_method;
char *usermap;
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 */
+ int remote_hostname_resolv; /* +1 = remote_hostname is known to resolve to client's IP address;
+ -1 = remote_hostname is known NOT to resolve to client's IP address;
+ 0 = we have not done the forward DNS lookup yet */
char *remote_port; /* text rep of remote port */
CAC_state canAcceptConnections; /* postmaster connection status */