]> granicus.if.org Git - postgresql/commitdiff
Support host names in pg_hba.conf
authorPeter Eisentraut <peter_e@gmx.net>
Fri, 15 Oct 2010 19:53:39 +0000 (22:53 +0300)
committerPeter Eisentraut <peter_e@gmx.net>
Fri, 15 Oct 2010 19:56:18 +0000 (22:56 +0300)
Peter Eisentraut, reviewed by KaiGai Kohei and Tom Lane

doc/src/sgml/client-auth.sgml
src/backend/libpq/hba.c
src/backend/libpq/pg_hba.conf.sample
src/backend/postmaster/postmaster.c
src/include/libpq/hba.h
src/include/libpq/libpq-be.h

index 5cf2f29db91bf79f45e6a4b263c6242dc781dd08..ab96af8f66da24e2f3a6e83b6ec178511b4d250c 100644 (file)
@@ -80,9 +80,9 @@
    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>
@@ -218,13 +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
-       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.
@@ -233,14 +237,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.
@@ -259,6 +256,67 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
        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.
@@ -511,12 +569,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 +582,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 +611,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 +621,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 +631,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
index 0227f9b67c768ef8fc49d83012589344b7456431..3f50349baf4e86531e2f5ac7c9c69c516e6775ce 100644 (file)
@@ -102,8 +102,8 @@ pg_isblank(const char c)
  * 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.
  */
@@ -198,6 +198,8 @@ next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
 
        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 ||
@@ -540,6 +542,102 @@ 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 = 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.
  */
@@ -782,12 +880,12 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
                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;
@@ -816,7 +914,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,13 +933,24 @@ 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 = '/';      /* 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)
                                {
@@ -852,7 +966,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 +1483,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:
index e1017cf28cd61d840f50a207631cd13b1b79b031..87fed80eedf5eb9106b047f73a1fdd1105b3dd33 100644 (file)
@@ -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.)
 #
 # 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
@@ -70,7 +71,7 @@
 
 @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@
index 210d930c9a5f1e322b5b93bc03639cda11d48688..8caa62ad0e00bc85b18a4c198b1ccf67c0c84290 100644 (file)
@@ -3422,6 +3422,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
index b8e8df08e0a5df0358cd5c100fb54ffd9fc9930c..eb6637f1c74854181c0e44c849d1094a5ac61e0f 100644 (file)
@@ -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;
index 83628c13f9f2161bfa64e466979e2110f2a563c6..1bc597c6acbd49959f0b44b2000840c816c26ecd 100644 (file)
@@ -109,6 +109,10 @@ 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 */
+       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 */