Thread: pg_hba.conf: samehost and samenet

pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
I love using postgresql, and have for a long time. I'm involved with
almost a hundred postgresql installs. But this is the first time I've
gotten into the code.

Renumbering networks happens often, and will happen more frequently as
IPv4 space runs low. The IP based restrictions in pg_hba.conf is one of
the places where renumbering can break running installs. In addition
when postgresql is run in BSD jails, 127.0.0.1 is not available for use
in pg_hba.conf.

It would be great if, in the cidr-address field of pg_hba.conf, we could
specify "samehost" and "samenet". These special values use the local
hosts network interface addresses. "samehost" allows an IP assigned to
the local machine. "samenet" allows any host on the subnets connected to
the local machine.

This is similar to the "sameuser" value that's allowed in the database
field.

A change like this would enable admins like myself to distribute
postgresql with something like this in the default pg_hba.conf file:

host      all     all    samenet     md5
hostssl   all     all   0.0.0.0/0    md5

I've attached an initial patch which implements "samehost" and
"samenet". The patch looks more invasive than it really is, due to
necessary indentation change (ie: a if block), and moving some code into
a separate function.

Thanks for your time. How can I help get a feature like this into
postgresql?

Cheers,

Stef
diff --git a/configure b/configure
index 61b3c72..7bcfcec 100755
*** a/configure
--- b/configure
*************** done
*** 9642,9648 ****



! for ac_header in crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h 
  do
  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
  if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
--- 9642,9649 ----



!
! for ac_header in crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h 
  do
  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
  if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
*************** fi
*** 17278,17284 ****



! for ac_func in cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs 
  do
  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
  { $as_echo "$as_me:$LINENO: checking for $ac_func" >&5
--- 17279,17286 ----



!
! for ac_func in cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs 
  do
  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
  { $as_echo "$as_me:$LINENO: checking for $ac_func" >&5
diff --git a/configure.in b/configure.in
index 505644a..bc37b1b 100644
*** a/configure.in
--- b/configure.in
*************** AC_SUBST(OSSP_UUID_LIBS)
*** 962,968 ****
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
--- 962,968 ----
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
*************** PGAC_VAR_INT_TIMEZONE
*** 1141,1147 ****
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
--- 1141,1147 ----
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index b3df7ee..ac99cce 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,30 ****
--- 25,34 ----
  #include <arpa/inet.h>
  #include <unistd.h>

+ #ifdef HAVE_GETIFADDRS
+ #include <ifaddrs.h>
+ #endif
+
  #include "libpq/ip.h"
  #include "libpq/libpq.h"
  #include "regex/regex.h"
*************** check_db(const char *dbname, const char
*** 564,569 ****
--- 568,685 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip (SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ #ifdef HAVE_GETIFADDRS
+
+ static bool
+ check_same_host_or_net (SockAddr *raddr, bool host)
+ {
+     bool result = false;
+     struct ifaddrs *ifa, *l;
+     struct sockaddr_storage mask;
+
+     if (getifaddrs (&ifa) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Could not get network interface addresses, when checking authentication")));
+         return false;
+     }
+
+     for (l = ifa; !result && l; l = l->ifa_next)
+     {
+         if (l->ifa_addr && l->ifa_netmask)
+         {
+             /* Make a fully 1's netmask of appropriate length */
+             if (host)
+             {
+                 pg_sockaddr_cidr_mask (&mask, NULL, l->ifa_addr->sa_family);
+                 result = check_ip (raddr, l->ifa_addr, (struct sockaddr*)&mask);
+             }
+
+             /* Use the netmask of the interface itself */
+             else
+             {
+                 result = check_ip (raddr, l->ifa_addr, l->ifa_netmask);
+             }
+         }
+     }
+
+     freeifaddrs (ifa);
+     return result;
+
+     return result;
+ }
+
+ #endif /* HAVE_GETIFADDRS */
+
+ static bool
+ check_same_host (SockAddr *raddr)
+ {
+ #ifdef HAVE_GETIFADDRS
+     return check_same_host_or_net (raddr, true);
+ #else
+     ereport(LOG,
+             (errcode(ERRCODE_WARNING),
+              errmsg("No support for lookup of network interface addresses, when checking authentication")));
+     return false;
+ #endif
+ }
+
+ static bool
+ check_same_net (SockAddr *raddr)
+ {
+ #ifdef HAVE_GETIFADDRS
+     return check_same_host_or_net (raddr, false);
+ #else
+     ereport(LOG,
+             (errcode(ERRCODE_WARNING),
+              errmsg("No support for lookup of network interface addresses, when checking authentication")));
+     return false;
+ #endif
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 710,808 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 826,945 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp (token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp (token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1144,1179 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1281,1304 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip (&port->raddr, (struct sockaddr*)&hba->addr,
!                                (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!                 if (!check_same_host (&port->raddr))
                      continue;
!                 break;
!             case nmSameNet:
!                 if (!check_same_net (&port->raddr))
!                     continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..835dc28 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..c6775e6 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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.
+ # You can also specify "samehost" to limit connections to those from addresses
+ # of the local machine. Or you can specify "samenet" to limit connections
+ # to addresses on the subnets of the local network.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8083fdc..61bc286 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index ba807f4..c2f453b 100644
*** a/src/include/pg_config.h.in
--- b/src/include/pg_config.h.in
***************
*** 176,181 ****
--- 176,184 ----
  /* Define to 1 if you have the `gethostbyname_r' function. */
  #undef HAVE_GETHOSTBYNAME_R

+ /* Define to 1 if you have the `getifaddrs' function. */
+ #undef HAVE_GETIFADDRS
+
  /* Define to 1 if you have the `getopt' function. */
  #undef HAVE_GETOPT

***************
*** 215,220 ****
--- 218,226 ----
  /* Define to 1 if you have the <ieeefp.h> header file. */
  #undef HAVE_IEEEFP_H

+ /* Define to 1 if you have the <ifaddrs.h> header file. */
+ #undef HAVE_IFADDRS_H
+
  /* Define to 1 if you have the `inet_aton' function. */
  #undef HAVE_INET_ATON


Re: pg_hba.conf: samehost and samenet

From
Magnus Hagander
Date:
On Fri, Aug 14, 2009 at 00:50, Stef Walter<stef-list@memberwebs.com> wrote:
> I love using postgresql, and have for a long time. I'm involved with
> almost a hundred postgresql installs. But this is the first time I've
> gotten into the code.
>
> Renumbering networks happens often, and will happen more frequently as
> IPv4 space runs low. The IP based restrictions in pg_hba.conf is one of
> the places where renumbering can break running installs. In addition
> when postgresql is run in BSD jails, 127.0.0.1 is not available for use
> in pg_hba.conf.
>
> It would be great if, in the cidr-address field of pg_hba.conf, we could
> specify "samehost" and "samenet". These special values use the local
> hosts network interface addresses. "samehost" allows an IP assigned to
> the local machine. "samenet" allows any host on the subnets connected to
> the local machine.
>
> This is similar to the "sameuser" value that's allowed in the database
> field.
>
> A change like this would enable admins like myself to distribute
> postgresql with something like this in the default pg_hba.conf file:
>
> host      all     all   samenet         md5
> hostssl   all     all   0.0.0.0/0       md5


Seems like a reasonable feature - especially the samehost part.


> I've attached an initial patch which implements "samehost" and
> "samenet". The patch looks more invasive than it really is, due to
> necessary indentation change (ie: a if block), and moving some code into
> a separate function.

A couple of comments on the patch:

* In general, don't include configure in the patch. Just configure.in.
Makes it easier to read, and configure is normally built by the
committer anyway.

* How portable is this? For starters is clearly doesn't do Windows,
which would need to be investigated for similar functionality, but how
many others support getifaddr()? From what I can tell it's not in
POSIX, at least.

* The checks for "not supported" should happen at parsing time, not at runtime.

* It needs to include documentation changes


I haven't looked at the guts of the patch yet, those are just a couple
of first questions.


> Thanks for your time. How can I help get a feature like this into
> postgresql?

Please add it to the open commitfest
(https://commitfest.postgresql.org/action/commitfest_view/open). This
will cause it to be reviewed during the next commitfest, and then you
just need to be around to answer any questions that reviewers come up
with :-)


-- Magnus HaganderMe: http://www.hagander.net/Work: http://www.redpill-linpro.com/


Re: pg_hba.conf: samehost and samenet

From
Tom Lane
Date:
Magnus Hagander <magnus@hagander.net> writes:
> On Fri, Aug 14, 2009 at 00:50, Stef Walter<stef-list@memberwebs.com> wrote:
>> It would be great if, in the cidr-address field of pg_hba.conf, we could
>> specify "samehost" and "samenet".

> Seems like a reasonable feature - especially the samehost part.

ISTM people have traditionally used 127.0.0.1 and ::1 for "samehost"
behavior.  What's being suggested here is a tad more flexible but
hardly a huge advance.  As for "samenet", personally I'd be scared to
death of something like that --- who knows how wide the OS will
think your "net" is?  (Think cable modem users on 10.x.x.x ...)
Using samenet in a conf file that's being handed out to random users
seems impossibly dangerous.

However, I wouldn't object too much if it weren't for this:

> * How portable is this? For starters is clearly doesn't do Windows,
> which would need to be investigated for similar functionality, but how
> many others support getifaddr()? From what I can tell it's not in
> POSIX, at least.

I don't see it on HPUX, for one.  Unless a portable solution can be
found I don't think we can consider this.  We're not in the habit
of exposing significant functionality that's only available on some
platforms.
        regards, tom lane


Re: pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
Magnus Hagander wrote:
> 
> A couple of comments on the patch:

Thanks I'll keep these in mind, as things progress and for future patches.

> * In general, don't include configure in the patch. Just configure.in.
> Makes it easier to read, and configure is normally built by the
> committer anyway.
> 
> * How portable is this? For starters is clearly doesn't do Windows,
> which would need to be investigated for similar functionality, but how
> many others support getifaddr()? From what I can tell it's not in
> POSIX, at least.

I'll look further into this, and about compat shims. getifaddrs() is on
the BSDs, Linux and Mac OS.


> Please add it to the open commitfest
> (https://commitfest.postgresql.org/action/commitfest_view/open). This
> will cause it to be reviewed during the next commitfest, and then you
> just need to be around to answer any questions that reviewers come up
> with :-)

Cool, I'll do that once we've worked out the kinks here. Is the right
way to go about it?

Cheers,

Stef




Re: pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
Tom Lane wrote:
> Magnus Hagander <magnus@hagander.net> writes:
>> On Fri, Aug 14, 2009 at 00:50, Stef Walter<stef-list@memberwebs.com> wrote:
>>> It would be great if, in the cidr-address field of pg_hba.conf, we could
>>> specify "samehost" and "samenet".
> 
>> Seems like a reasonable feature - especially the samehost part.
> 
> ISTM people have traditionally used 127.0.0.1 and ::1 for "samehost"
> behavior.  

Yes for sure. As noted in the original email 127.0.0.1 doesn't work as
you would expect in BSD jails. As it currently stands, you have to put
the local IP address to achieve similar access control. This causes
major pains when renumbering or dealing with postgresql hosted in large
amounts of jails.

Another way we could sort of get around most of these renumbering
problems, is by the ability to include host names in pg_hba.conf, rather
than IP addresses. I first set out to implement this, but as advised in
"How to Contribute" looked around the mailing lists for previous
discussion on the topic and found this:

http://archives.postgresql.org/pgsql-hackers/2008-06/msg00569.php

There seems to be no consensus in the postgresql community about this
feature, and its implementation. The last guy who tried to work on it
got scared away, and so I decided to try an approach that might be more
palatable.

I'm willing to put in the work on either approach, and I could revive
discussion about host names in pg_hba.conf if that's more desirable.

What's being suggested here is a tad more flexible but
> hardly a huge advance.  As for "samenet", personally I'd be scared to
> death of something like that --- who knows how wide the OS will
> think your "net" is?  (Think cable modem users on 10.x.x.x ...)
> Using samenet in a conf file that's being handed out to random users
> seems impossibly dangerous.

I understand what you're saying. In this case it would be handed out to
hosted clients and those sorts of users. ie: a controlled environment.
Obviously this wouldn't go into the default postgresql pg_hba.conf.

> However, I wouldn't object too much if it weren't for this:
> 
>> * How portable is this? For starters is clearly doesn't do Windows,
>> which would need to be investigated for similar functionality, but how
>> many others support getifaddr()? From what I can tell it's not in
>> POSIX, at least.
> 
> I don't see it on HPUX, for one.  Unless a portable solution can be
> found I don't think we can consider this.  We're not in the habit
> of exposing significant functionality that's only available on some
> platforms.

True. I could build compatibility getifaddrs for various systems, if the
community thought this patch was worth it, and would otherwise accept
the patch.

Cheers,

Stef



Re: pg_hba.conf: samehost and samenet

From
Tom Lane
Date:
Stef Walter <stef-list@memberwebs.com> writes:
> True. I could build compatibility getifaddrs for various systems, if the
> community thought this patch was worth it, and would otherwise accept
> the patch.

If you can do that I think it'd remove the major objection.
        regards, tom lane


Re: pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
Attached is a new patch, which I hope addresses all the concerns raised.

Magnus Hagander wrote:
>> I've attached an initial patch which implements "samehost" and
>> "samenet". The patch looks more invasive than it really is, due to
>> necessary indentation change (ie: a if block), and moving some code into
>> a separate function.
> 
> A couple of comments on the patch:
> 
> * In general, don't include configure in the patch. Just configure.in.
> Makes it easier to read, and configure is normally built by the
> committer anyway.

Removed configure and pg_config.h.in from the patch.

> * How portable is this? For starters is clearly doesn't do Windows,
> which would need to be investigated for similar functionality, but how
> many others support getifaddr()? From what I can tell it's not in
> POSIX, at least.

getifaddr() is at least supported on *BSD, Linux and AIX.

In the new patch, I've added support for other unixes like Solaris,
HPUX, IRIC, SCO Unix (using the SIOCGIFCONF ioctl). Also included is
Win32 support (using winsock's SIO_GET_INTERFACE_LIST).

Obviously I don't have all of the above proprietary unixes to test on,
but I've studied documentation, and I believe that the code in the patch
will run on those systems.

> * It needs to include documentation changes

Done.

> Please add it to the open commitfest
> (https://commitfest.postgresql.org/action/commitfest_view/open). This
> will cause it to be reviewed during the next commitfest, and then you
> just need to be around to answer any questions that reviewers come up
> with :-)

I need some sort of a login to add a patch to the commit fest. Is that
something I can get? Or is there someone I should ask to add my patch to
the commit fest? I hope I'm not being dense and missing something
obvious. :)

Cheers,

Stef



Re: pg_hba.conf: samehost and samenet

From
Tom Lane
Date:
Stef Walter <stef-list@memberwebs.com> writes:
> Magnus Hagander wrote:
>> Please add it to the open commitfest
>> (https://commitfest.postgresql.org/action/commitfest_view/open). This
>> will cause it to be reviewed during the next commitfest, and then you
>> just need to be around to answer any questions that reviewers come up
>> with :-)

> I need some sort of a login to add a patch to the commit fest. Is that
> something I can get?

Go here:
http://www.postgresql.org/community/signup

The login page for the commitfest app really ought to provide a link
to that ... Robert?
        regards, tom lane


Re: pg_hba.conf: samehost and samenet

From
Robert Haas
Date:
On Tue, Aug 18, 2009 at 10:11 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:
> Stef Walter <stef-list@memberwebs.com> writes:
>> Magnus Hagander wrote:
>>> Please add it to the open commitfest
>>> (https://commitfest.postgresql.org/action/commitfest_view/open). This
>>> will cause it to be reviewed during the next commitfest, and then you
>>> just need to be around to answer any questions that reviewers come up
>>> with :-)
>
>> I need some sort of a login to add a patch to the commit fest. Is that
>> something I can get?
>
> Go here:
> http://www.postgresql.org/community/signup
>
> The login page for the commitfest app really ought to provide a link
> to that ... Robert?

Done.

...Robert


Re: pg_hba.conf: samehost and samenet

From
Magnus Hagander
Date:
On Wed, Aug 19, 2009 at 03:58, Stef Walter<stef-list@memberwebs.com> wrote:
> Attached is a new patch, which I hope addresses all the concerns raised.

I think you forgot to actually attach the patch....

(others have taken care of the question about login already I see)


-- Magnus HaganderMe: http://www.hagander.net/Work: http://www.redpill-linpro.com/


Re: pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
Magnus Hagander wrote:
> On Wed, Aug 19, 2009 at 03:58, Stef Walter<stef-list@memberwebs.com> wrote:
>> Attached is a new patch, which I hope addresses all the concerns raised.
>
> I think you forgot to actually attach the patch....

Whoops. Here it is.

Stef

diff --git a/configure.in b/configure.in
index 505644a..bc37b1b 100644
*** a/configure.in
--- b/configure.in
*************** AC_SUBST(OSSP_UUID_LIBS)
*** 962,968 ****
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
--- 962,968 ----
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
*************** PGAC_VAR_INT_TIMEZONE
*** 1141,1147 ****
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
--- 1141,1147 ----
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index ad4d084..e88c796 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 244,249 ****
--- 244,256 ----
         support for IPv6 addresses.
        </para>

+       <para>Instead of an <replaceable>CIDR-address</replaceable>, you can specify
+        the values <literal>samehost</literal> or <literal>samenet</literal>. To
+        match any address on the subnets connected to the local machine, specify
+        <literal>samenet</literal>. By specifying <literal>samehost</literal>, any
+        addresses present on the network interfaces of local machine will match.
+       </para>
+
        <para>
         This field only applies to <literal>host</literal>,
         <literal>hostssl</literal>, and <literal>hostnossl</> records.
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index b3df7ee..5d56603 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_db(const char *dbname, const char
*** 564,569 ****
--- 564,660 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip (SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ typedef struct CheckNetwork {
+     NetMethod method;
+     SockAddr *raddr;
+     bool result;
+ } CheckNetwork;
+
+ static void
+ callback_check_network (struct sockaddr *addr, struct sockaddr *netmask, void *data)
+ {
+     CheckNetwork *cn = data;
+     struct sockaddr_storage mask;
+
+     /* Already found a match */
+     if (cn->result)
+         return;
+
+     /* Make a fully 1's netmask of appropriate length */
+     if (cn->method == nmSameHost)
+     {
+         pg_sockaddr_cidr_mask (&mask, NULL, addr->sa_family);
+         cn->result = check_ip (cn->raddr, addr, (struct sockaddr*)&mask);
+     }
+
+     /* Use the netmask of the interface itself */
+     else
+     {
+         cn->result = check_ip (cn->raddr, addr, netmask);
+     }
+ }
+
+ static bool
+ check_same_host_or_net (SockAddr *raddr, NetMethod method)
+ {
+     CheckNetwork cn;
+     cn.method = method;
+     cn.raddr = raddr;
+     cn.result = false;
+
+     if (pg_foreach_ifaddr (callback_check_network, &cn) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Error enumerating network interfaces")));
+         return false;
+     }
+
+     return cn.result;
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 710,808 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 801,920 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp (token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp (token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1144,1179 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1256,1276 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip (&port->raddr, (struct sockaddr*)&hba->addr,
!                                (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!             case nmSameNet:
!                 if (!check_same_host_or_net (&port->raddr, hba->net_method))
                      continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..5392dc5 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
*************** pg_promote_v4_to_v6_mask(struct sockaddr
*** 476,478 ****
--- 484,600 ----
  }

  #endif   /* HAVE_IPV6 */
+
+ #ifdef HAVE_GETIFADDRS
+
+ #include <ifaddrs.h>
+
+ static int
+ foreach_ifaddr_getifaddrs(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifaddrs *ifa, *l;
+
+     if (getifaddrs (&ifa) < 0)
+         return -1;
+
+     for (l = ifa; l; l = l->ifa_next)
+         (callback) (l->ifa_addr, l->ifa_netmask, cb_data);
+
+     freeifaddrs (ifa);
+     return 0;
+ }
+
+ #endif /* HAVE_GETIFADDRS */
+
+ #ifdef WIN32
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ static int
+ foreach_ifaddr_win32(PgIfAddrCallback callback, void * cb_data)
+ {
+     INTERFACE_INFO ii[64];
+     unsigned long length, i;
+     SOCKET sock;
+
+     sock = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+     if (sock == SOCKET_ERROR)
+         return -1;
+
+     if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, 0, 0, &ii,
+         sizeof (ii), &length, 0, 0) == SOCKET_ERROR)
+     {
+         closesocket(sock);
+         return -1;
+     }
+
+     for (i = 0; i < length / sizeof (INTERFACE_INFO); ++i)
+         (callback)((struct sockaddr*)&ii[i].iiAddress,
+                    (struct sockaddr*)&ii[i].iiNetmask, cb_data);
+
+     closesocket(sock);
+     return 0;
+ }
+
+ #else /* !WIN32 */
+
+ #include <sys/ioctl.h>
+ #include <net/if.h>
+
+ static int
+ foreach_ifaddr_ifconf(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifconf ifc;
+     struct ifreq addr, mask;
+     char buffer[10240];
+     int sock;
+     int i, total;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     ifc.ifc_buf = buffer;
+     ifc.ifc_len = sizeof(buffer);
+
+     if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
+     {
+         close(sock);
+         return -1;
+     }
+
+     total = ifc.ifc_len / sizeof(struct ifreq);
+
+     for (i = 0; i < total; ++i)
+     {
+         memset (&addr, 0, sizeof (addr));
+         memcpy (addr.ifr_name, ifc.ifc_req[i].ifr_name, sizeof (addr.ifr_name));
+         memset (&mask, 0, sizeof (mask));
+         memcpy (mask.ifr_name, ifc.ifc_req[i].ifr_name, sizeof (mask.ifr_name));
+
+         if (ioctl(sock, SIOCGIFADDR, &addr, sizeof(addr)) == 0 &&
+             ioctl(sock, SIOCGIFNETMASK, &mask, sizeof(mask)) == 0)
+             (callback)(&addr.ifr_addr, &mask.ifr_netmask, cb_data);
+     }
+
+     close (sock);
+     return 0;
+ }
+
+ #endif
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+ #ifdef WIN32
+     return foreach_ifaddr_win32(callback, cb_data);
+ #else /* !WIN32 */
+ #ifdef HAVE_GETIFADDRS
+     return foreach_ifaddr_getifaddrs(callback, cb_data);
+ #else /* !HAVE_GETIFADDRS */
+     return foreach_ifaddr_ifconf(callback, cb_data);
+ #endif
+ #endif /* !WIN32 */
+ }
+
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..c6775e6 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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.
+ # You can also specify "samehost" to limit connections to those from addresses
+ # of the local machine. Or you can specify "samenet" to limit connections
+ # to addresses on the subnets of the local network.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8083fdc..61bc286 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h
index 1934957..9bd562c 100644
*** a/src/include/libpq/ip.h
--- b/src/include/libpq/ip.h
*************** extern void pg_promote_v4_to_v6_mask(str
*** 47,50 ****
--- 47,56 ----
  #define IS_AF_UNIX(fam) (0)
  #endif

+ typedef void (*PgIfAddrCallback)(struct sockaddr * addr,
+                                  struct sockaddr * netmask,
+                                  void * cb_data);
+
+ extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data);
+
  #endif   /* IP_H */


Re: pg_hba.conf: samehost and samenet

From
Magnus Hagander
Date:
On Wed, Aug 19, 2009 at 15:02, Stef Walter<stef-list@memberwebs.com> wrote:
> Magnus Hagander wrote:
>> On Wed, Aug 19, 2009 at 03:58, Stef Walter<stef-list@memberwebs.com> wrote:
>>> Attached is a new patch, which I hope addresses all the concerns raised.
>>
>> I think you forgot to actually attach the patch....
>
> Whoops. Here it is.

Is there any actual advantage to using getifaddr() on Linux, and not
just use SIOCGIFCONF for all Unixen?


-- Magnus HaganderMe: http://www.hagander.net/Work: http://www.redpill-linpro.com/


Re: pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
Magnus Hagander wrote:
> On Wed, Aug 19, 2009 at 15:02, Stef Walter<stef-list@memberwebs.com> wrote:
>> Magnus Hagander wrote:
>>> On Wed, Aug 19, 2009 at 03:58, Stef Walter<stef-list@memberwebs.com> wrote:
>>>> Attached is a new patch, which I hope addresses all the concerns raised.
>>> I think you forgot to actually attach the patch....
>> Whoops. Here it is.
>
> Is there any actual advantage to using getifaddr() on Linux,

It is in my opinion, it is the most modern and maintainable of the
methods for obtaining network interface address information. Various
unixes have added support for getifaddr() over the years, and (again my
opinion) would probably continue to do so.

> and not
> just use SIOCGIFCONF for all Unixen?

I do know that using SIOCGIFCONF on AIX comes with strange wrinkles and
variable length data structures etc... getifaddrs() on AIX is a far more
maintainable interface.

That said, I'll adjust the patch as you feel is best for the long term
inclusion in the postgresql source.

Cheers,

Stef

Re: pg_hba.conf: samehost and samenet

From
Alvaro Herrera
Date:
Stef Walter wrote:
> Magnus Hagander wrote:

> > and not just use SIOCGIFCONF for all Unixen?
> 
> I do know that using SIOCGIFCONF on AIX comes with strange wrinkles and
> variable length data structures etc... getifaddrs() on AIX is a far more
> maintainable interface.

Clearly the getifaddrs code looks nicer.  How can we distinguish the
SIOCGIFCONF implementations that have wrinkles from those that don't?
Is there some autoconf macro?

Something to keep in mind -- my getifaddrs(3) manpage says that on BSD
it can return addresses that have ifa_addr set to NULL, which your code
doesn't seem to check.

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support


Re: pg_hba.conf: samehost and samenet

From
Alvaro Herrera
Date:
Something is very wrong here -- this message does not have a
message-id!


Stef Walter wrote:
> Magnus Hagander wrote:
> > On Wed, Aug 19, 2009 at 15:02, Stef Walter<stef-list@memberwebs.com> wrote:
> >> Magnus Hagander wrote:
> >>> On Wed, Aug 19, 2009 at 03:58, Stef Walter<stef-list@memberwebs.com> wrote:
> >>>> Attached is a new patch, which I hope addresses all the concerns raised.
> >>> I think you forgot to actually attach the patch....
> >> Whoops. Here it is.
> > 
> > Is there any actual advantage to using getifaddr() on Linux, 
> 
> It is in my opinion, it is the most modern and maintainable of the
> methods for obtaining network interface address information. Various
> unixes have added support for getifaddr() over the years, and (again my
> opinion) would probably continue to do so.
> 
> > and not
> > just use SIOCGIFCONF for all Unixen?
> 
> I do know that using SIOCGIFCONF on AIX comes with strange wrinkles and
> variable length data structures etc... getifaddrs() on AIX is a far more
> maintainable interface.
> 
> That said, I'll adjust the patch as you feel is best for the long term
> inclusion in the postgresql source.
> 
> Cheers,
> 
> Stef
> 
> 
> -- 
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers


-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support


Re: pg_hba.conf: samehost and samenet

From
Magnus Hagander
Date:
2009/8/25 Alvaro Herrera <alvherre@commandprompt.com>:
> Stef Walter wrote:
>> Magnus Hagander wrote:
>
>> > and not just use SIOCGIFCONF for all Unixen?
>>
>> I do know that using SIOCGIFCONF on AIX comes with strange wrinkles and
>> variable length data structures etc... getifaddrs() on AIX is a far more
>> maintainable interface.
>
> Clearly the getifaddrs code looks nicer.  How can we distinguish the
> SIOCGIFCONF implementations that have wrinkles from those that don't?
> Is there some autoconf macro?

If there are some that do have it, and these platforms support
getifaddrs(), that certainly seems like a worthwhile reason. I was
just looking for the case when the SIOGIFCONF code would be identical
on all platforms - in which case we'd jus tbe maintaining some
unnecessary code. So it seems fine here.


> Something to keep in mind -- my getifaddrs(3) manpage says that on BSD
> it can return addresses that have ifa_addr set to NULL, which your code
> doesn't seem to check.

Eek. This is not defined by any standard, is it? I wonder how many
different behaviours we can find there :(

-- Magnus HaganderMe: http://www.hagander.net/Work: http://www.redpill-linpro.com/


Re: pg_hba.conf: samehost and samenet

From
Alvaro Herrera
Date:
Stef Walter wrote:
> [Looks like my response to this never made it to the mailing list,
> sending again...]

Your original message did not carry a Message-Id header, and neither
does this one (at least the copy I got).  Are you doing something weird
to the message?  This worries me, because we intend for message-ids to
become our primary keys into the archives, and we rely very heavily on
them (for example on the commitfest webapp at
http://commitfest.postgresql.org).


> Magnus Hagander wrote:
> > 2009/8/25 Alvaro Herrera <alvherre@commandprompt.com>:
> >> Something to keep in mind -- my getifaddrs(3) manpage says that on BSD
> >> it can return addresses that have ifa_addr set to NULL, which your code
> >> doesn't seem to check.
> 
> Thanks for catching that. I've added a check, and attached a new patch.

[...]

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.


Re: pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
[Thanks for the heads up about the MessageID missing when posting this
previously. Was doing some mail filter development, and accidentally
left it in place... ]

Magnus Hagander wrote:
> 2009/8/25 Alvaro Herrera <alvherre@commandprompt.com>:
>> Something to keep in mind -- my getifaddrs(3) manpage says that on BSD
>> it can return addresses that have ifa_addr set to NULL, which your code
>> doesn't seem to check.

Thanks for catching that. I've added a check, and attached a new patch.

> Eek. This is not defined by any standard, is it? I wonder how many
> different behaviours we can find there :(

I've checked AIX, Linux, BSD and Mac OS and NULL ifa_addr's are
documented in all of them.

Cheers,

Stef



diff --git a/configure.in b/configure.in
index e545a1f..b77ce2b 100644
*** a/configure.in
--- b/configure.in
*************** AC_SUBST(OSSP_UUID_LIBS)
*** 969,975 ****
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
--- 969,975 ----
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
*************** PGAC_VAR_INT_TIMEZONE
*** 1148,1154 ****
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
--- 1148,1154 ----
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index ad4d084..e88c796 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 244,249 ****
--- 244,256 ----
         support for IPv6 addresses.
        </para>

+       <para>Instead of an <replaceable>CIDR-address</replaceable>, you can specify
+        the values <literal>samehost</literal> or <literal>samenet</literal>. To
+        match any address on the subnets connected to the local machine, specify
+        <literal>samenet</literal>. By specifying <literal>samehost</literal>, any
+        addresses present on the network interfaces of local machine will match.
+       </para>
+
        <para>
         This field only applies to <literal>host</literal>,
         <literal>hostssl</literal>, and <literal>hostnossl</> records.
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index e6f7db2..c2da3a0 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_db(const char *dbname, const char
*** 512,517 ****
--- 512,608 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip (SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ typedef struct CheckNetwork {
+     NetMethod method;
+     SockAddr *raddr;
+     bool result;
+ } CheckNetwork;
+
+ static void
+ callback_check_network (struct sockaddr *addr, struct sockaddr *netmask, void *data)
+ {
+     CheckNetwork *cn = data;
+     struct sockaddr_storage mask;
+
+     /* Already found a match */
+     if (cn->result)
+         return;
+
+     /* Make a fully 1's netmask of appropriate length */
+     if (cn->method == nmSameHost)
+     {
+         pg_sockaddr_cidr_mask (&mask, NULL, addr->sa_family);
+         cn->result = check_ip (cn->raddr, addr, (struct sockaddr*)&mask);
+     }
+
+     /* Use the netmask of the interface itself */
+     else
+     {
+         cn->result = check_ip (cn->raddr, addr, netmask);
+     }
+ }
+
+ static bool
+ check_same_host_or_net (SockAddr *raddr, NetMethod method)
+ {
+     CheckNetwork cn;
+     cn.method = method;
+     cn.raddr = raddr;
+     cn.result = false;
+
+     if (pg_foreach_ifaddr (callback_check_network, &cn) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Error enumerating network interfaces")));
+         return false;
+     }
+
+     return cn.result;
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 658,756 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 749,868 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp (token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp (token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1096,1131 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1208,1228 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip (&port->raddr, (struct sockaddr*)&hba->addr,
!                                (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!             case nmSameNet:
!                 if (!check_same_host_or_net (&port->raddr, hba->net_method))
                      continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..cc40879 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
*************** pg_promote_v4_to_v6_mask(struct sockaddr
*** 476,478 ****
--- 484,603 ----
  }

  #endif   /* HAVE_IPV6 */
+
+ #ifdef HAVE_GETIFADDRS
+
+ #include <ifaddrs.h>
+
+ static int
+ foreach_ifaddr_getifaddrs(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifaddrs *ifa, *l;
+
+     if (getifaddrs (&ifa) < 0)
+         return -1;
+
+     for (l = ifa; l; l = l->ifa_next)
+     {
+         if (l->ifa_addr && l->ifa_netmask)
+             (callback) (l->ifa_addr, l->ifa_netmask, cb_data);
+     }
+
+     freeifaddrs (ifa);
+     return 0;
+ }
+
+ #endif /* HAVE_GETIFADDRS */
+
+ #ifdef WIN32
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ static int
+ foreach_ifaddr_win32(PgIfAddrCallback callback, void * cb_data)
+ {
+     INTERFACE_INFO ii[64];
+     unsigned long length, i;
+     SOCKET sock;
+
+     sock = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+     if (sock == SOCKET_ERROR)
+         return -1;
+
+     if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, 0, 0, &ii,
+         sizeof (ii), &length, 0, 0) == SOCKET_ERROR)
+     {
+         closesocket(sock);
+         return -1;
+     }
+
+     for (i = 0; i < length / sizeof (INTERFACE_INFO); ++i)
+         (callback)((struct sockaddr*)&ii[i].iiAddress,
+                    (struct sockaddr*)&ii[i].iiNetmask, cb_data);
+
+     closesocket(sock);
+     return 0;
+ }
+
+ #else /* !WIN32 */
+
+ #include <sys/ioctl.h>
+ #include <net/if.h>
+
+ static int
+ foreach_ifaddr_ifconf(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifconf ifc;
+     struct ifreq addr, mask;
+     char buffer[10240];
+     int sock;
+     int i, total;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     ifc.ifc_buf = buffer;
+     ifc.ifc_len = sizeof(buffer);
+
+     if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
+     {
+         close(sock);
+         return -1;
+     }
+
+     total = ifc.ifc_len / sizeof(struct ifreq);
+
+     for (i = 0; i < total; ++i)
+     {
+         memset (&addr, 0, sizeof (addr));
+         memcpy (addr.ifr_name, ifc.ifc_req[i].ifr_name, sizeof (addr.ifr_name));
+         memset (&mask, 0, sizeof (mask));
+         memcpy (mask.ifr_name, ifc.ifc_req[i].ifr_name, sizeof (mask.ifr_name));
+
+         if (ioctl(sock, SIOCGIFADDR, &addr, sizeof(addr)) == 0 &&
+             ioctl(sock, SIOCGIFNETMASK, &mask, sizeof(mask)) == 0)
+             (callback)(&addr.ifr_addr, &mask.ifr_netmask, cb_data);
+     }
+
+     close (sock);
+     return 0;
+ }
+
+ #endif
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+ #ifdef WIN32
+     return foreach_ifaddr_win32(callback, cb_data);
+ #else /* !WIN32 */
+ #ifdef HAVE_GETIFADDRS
+     return foreach_ifaddr_getifaddrs(callback, cb_data);
+ #else /* !HAVE_GETIFADDRS */
+     return foreach_ifaddr_ifconf(callback, cb_data);
+ #endif
+ #endif /* !WIN32 */
+ }
+
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..c6775e6 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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.
+ # You can also specify "samehost" to limit connections to those from addresses
+ # of the local machine. Or you can specify "samenet" to limit connections
+ # to addresses on the subnets of the local network.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index b538ee4..5193f38 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h
index 1934957..9bd562c 100644
*** a/src/include/libpq/ip.h
--- b/src/include/libpq/ip.h
*************** extern void pg_promote_v4_to_v6_mask(str
*** 47,50 ****
--- 47,56 ----
  #define IS_AF_UNIX(fam) (0)
  #endif

+ typedef void (*PgIfAddrCallback)(struct sockaddr * addr,
+                                  struct sockaddr * netmask,
+                                  void * cb_data);
+
+ extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data);
+
  #endif   /* IP_H */





Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Abhijit Menon-Sen
Date:
(This is my review of the latest version of Stef Walter's samehost/net
patch, posted on 2009-09-17. See
http://archives.postgresql.org/message-id/4AB28043.3050109@memberwebs.com
for the original message.)

The patch applies and builds cleanly, and the samehost/samenet keywords
in pg_hba.conf work as advertised. I have no particular opinion on the
desirability of the feature, but I can see it would be useful in some
circumstances, as Stef described.

I think the patch is more or less ready, but I have a few minor
comments:

First, it needs to be reformatted to not use a space before the opening
parentheses in (some) function calls and definitions.

> *** a/doc/src/sgml/client-auth.sgml
> --- b/doc/src/sgml/client-auth.sgml
> [...]
>   
> +       <para>Instead of an <replaceable>CIDR-address</replaceable>, you can specify 
> +        the values <literal>samehost</literal> or <literal>samenet</literal>. To 
> +        match any address on the subnets connected to the local machine, specify 
> +        <literal>samenet</literal>. By specifying <literal>samehost</literal>, any 
> +        addresses present on the network interfaces of local machine will match.
> +       </para>
> + 

I'd suggest something like the following instead:
   <para>Instead of a <replaceable>CIDR-address</replaceable>, you can   specify <literal>samehost</literal> to match
anyof the server's own   IP addresses, or <literal>samenet</literal> to match any address in   a subnet that the server
belongsto.
 

> *** a/src/backend/libpq/hba.c
> --- b/src/backend/libpq/hba.c
> [...]
>
> +     else if (addr->sa_family == AF_INET &&
> +              raddr->addr.ss_family == AF_INET6)
> +     {
> +         /*
> +          * 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.
> +          */

How about this instead:
   If we're listening on IPv6 but the file specifies an IPv4 address to   match against, we promote the latter also to
anIPv6 address before   trying to match the client's address.
 

(The comment is repeated elsewhere.)

> + int
> + pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
> + {
> + #ifdef WIN32
> +     return foreach_ifaddr_win32(callback, cb_data);
> + #else /* !WIN32 */
> + #ifdef HAVE_GETIFADDRS
> +     return foreach_ifaddr_getifaddrs(callback, cb_data);
> + #else /* !HAVE_GETIFADDRS */
> +     return foreach_ifaddr_ifconf(callback, cb_data);
> + #endif
> + #endif /* !WIN32 */
> + }

First, writing it this way is less noisy:
   #ifdef WIN32       return foreach_ifaddr_win32(callback, cb_data);   #elif defined(HAVE_GETIFADDRS)       return
foreach_ifaddr_getifaddrs(callback,cb_data);   #else       return foreach_ifaddr_ifconf(callback, cb_data);   #endif
 

Second, I can't see that it makes very much difference, but why do it
this way at all? You could just have each of the three #ifdef blocks
define a function named pg_foreach_ifaddr() and be done with it. No
need for a fourth function.

> *** a/src/backend/libpq/pg_hba.conf.sample
> --- b/src/backend/libpq/pg_hba.conf.sample
> [...]
>
> + # You can also specify "samehost" to limit connections to those from addresses
> + # of the local machine. Or you can specify "samenet" to limit connections
> + # to addresses on the subnets of the local network.

This should be reworded to match the documentation change suggested
above.

-- ams


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Magnus Hagander
Date:
On Sun, Sep 20, 2009 at 05:59, Abhijit Menon-Sen <ams@toroid.org> wrote:
> I think the patch is more or less ready, but I have a few minor
> comments:
>
> First, it needs to be reformatted to not use a space before the opening
> parentheses in (some) function calls and definitions.
>
>> *** a/doc/src/sgml/client-auth.sgml
>> --- b/doc/src/sgml/client-auth.sgml
>> [...]
>>
>> +       <para>Instead of an <replaceable>CIDR-address</replaceable>, you can specify
>> +        the values <literal>samehost</literal> or <literal>samenet</literal>. To
>> +        match any address on the subnets connected to the local machine, specify
>> +        <literal>samenet</literal>. By specifying <literal>samehost</literal>, any
>> +        addresses present on the network interfaces of local machine will match.
>> +       </para>
>> +
>
> I'd suggest something like the following instead:
>
>    <para>Instead of a <replaceable>CIDR-address</replaceable>, you can
>    specify <literal>samehost</literal> to match any of the server's own
>    IP addresses, or <literal>samenet</literal> to match any address in
>    a subnet that the server belongs to.
>
>> *** a/src/backend/libpq/hba.c
>> --- b/src/backend/libpq/hba.c
>> [...]
>>
>> +     else if (addr->sa_family == AF_INET &&
>> +                      raddr->addr.ss_family == AF_INET6)
>> +     {
>> +             /*
>> +              * 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.
>> +              */
>
> How about this instead:
>
>    If we're listening on IPv6 but the file specifies an IPv4 address to
>    match against, we promote the latter also to an IPv6 address before
>    trying to match the client's address.
>
> (The comment is repeated elsewhere.)

That's actually a copy/paste from the code that's in hba.c now. That's
not reason not to fix it of course :-)


>> + int
>> + pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
>> + {
>> + #ifdef WIN32
>> +     return foreach_ifaddr_win32(callback, cb_data);
>> + #else /* !WIN32 */
>> + #ifdef HAVE_GETIFADDRS
>> +     return foreach_ifaddr_getifaddrs(callback, cb_data);
>> + #else /* !HAVE_GETIFADDRS */
>> +     return foreach_ifaddr_ifconf(callback, cb_data);
>> + #endif
>> + #endif /* !WIN32 */
>> + }
>
> First, writing it this way is less noisy:
>
>    #ifdef WIN32
>        return foreach_ifaddr_win32(callback, cb_data);
>    #elif defined(HAVE_GETIFADDRS)
>        return foreach_ifaddr_getifaddrs(callback, cb_data);
>    #else
>        return foreach_ifaddr_ifconf(callback, cb_data);
>    #endif
>
> Second, I can't see that it makes very much difference, but why do it
> this way at all? You could just have each of the three #ifdef blocks
> define a function named pg_foreach_ifaddr() and be done with it. No
> need for a fourth function.

That was my thought as well when I looked at the patch. It'd be
clearer and I think more in line with what we do at other places.


-- Magnus HaganderMe: http://www.hagander.net/Work: http://www.redpill-linpro.com/


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Thanks for your review!

Abhijit Menon-Sen wrote:
> First, it needs to be reformatted to not use a space before the opening
> parentheses in (some) function calls and definitions.

Fixed in the attached patch.

>> *** a/doc/src/sgml/client-auth.sgml
>> --- b/doc/src/sgml/client-auth.sgml
>> [...]
>>
> I'd suggest something like the following instead:
>
>     <para>Instead of a <replaceable>CIDR-address</replaceable>, you can
>     specify <literal>samehost</literal> to match any of the server's own
>     IP addresses, or <literal>samenet</literal> to match any address in
>     a subnet that the server belongs to.

Updated in attached patch.

>> *** a/src/backend/libpq/hba.c
>> --- b/src/backend/libpq/hba.c
>> [...]
>>
>> +     else if (addr->sa_family == AF_INET &&
>> +              raddr->addr.ss_family == AF_INET6)
>> +     {
>> +         /*
>> +          * 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.
>> +          */
>
> How about this instead:
>
>     If we're listening on IPv6 but the file specifies an IPv4 address to
>     match against, we promote the latter also to an IPv6 address before
>     trying to match the client's address.

As Magnus noted, this is a comment already present in the postgresql
code. I simply moved it into a function. However, I've attached a second
patch which fixes this issue, and can be committed at your discretion.

> You could just have each of the three #ifdef blocks
> define a function named pg_foreach_ifaddr() and be done with it. No
> need for a fourth function.

Done.

>> *** a/src/backend/libpq/pg_hba.conf.sample
>> --- b/src/backend/libpq/pg_hba.conf.sample
>> [...]
>>
>> + # You can also specify "samehost" to limit connections to those from addresses
>> + # of the local machine. Or you can specify "samenet" to limit connections
>> + # to addresses on the subnets of the local network.
>
> This should be reworded to match the documentation change suggested
> above.

Done.

Cheers,

Stef

diff --git a/configure.in b/configure.in
index e545a1f..b77ce2b 100644
*** a/configure.in
--- b/configure.in
*************** AC_SUBST(OSSP_UUID_LIBS)
*** 969,975 ****
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
--- 969,975 ----
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
*************** PGAC_VAR_INT_TIMEZONE
*** 1148,1154 ****
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
--- 1148,1154 ----
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index ad4d084..e5152f4 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 244,249 ****
--- 244,255 ----
         support for IPv6 addresses.
        </para>

+       <para>Instead of a <replaceable>CIDR-address</replaceable>, you can specify
+        <literal>samehost</literal> to match any of the server's own IP addresses,
+        or <literal>samenet</literal> to match any address in a subnet that the
+        server belongs to.
+       </para>
+
        <para>
         This field only applies to <literal>host</literal>,
         <literal>hostssl</literal>, and <literal>hostnossl</> records.
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index e6f7db2..702971a 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_db(const char *dbname, const char
*** 512,517 ****
--- 512,608 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ typedef struct CheckNetwork {
+     NetMethod method;
+     SockAddr *raddr;
+     bool result;
+ } CheckNetwork;
+
+ static void
+ callback_check_network(struct sockaddr *addr, struct sockaddr *netmask, void *data)
+ {
+     CheckNetwork *cn = data;
+     struct sockaddr_storage mask;
+
+     /* Already found a match */
+     if (cn->result)
+         return;
+
+     /* Make a fully 1's netmask of appropriate length */
+     if (cn->method == nmSameHost)
+     {
+         pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
+         cn->result = check_ip(cn->raddr, addr, (struct sockaddr*)&mask);
+     }
+
+     /* Use the netmask of the interface itself */
+     else
+     {
+         cn->result = check_ip(cn->raddr, addr, netmask);
+     }
+ }
+
+ static bool
+ check_same_host_or_net(SockAddr *raddr, NetMethod method)
+ {
+     CheckNetwork cn;
+     cn.method = method;
+     cn.raddr = raddr;
+     cn.result = false;
+
+     if (pg_foreach_ifaddr(callback_check_network, &cn) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Error enumerating network interfaces")));
+         return false;
+     }
+
+     return cn.result;
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 658,756 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 749,868 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp(token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp(token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1096,1131 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1208,1228 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr,
!                               (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!             case nmSameNet:
!                 if (!check_same_host_or_net(&port->raddr, hba->net_method))
                      continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..2aaab2e 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
*************** pg_promote_v4_to_v6_mask(struct sockaddr
*** 476,478 ****
--- 484,588 ----
  }

  #endif   /* HAVE_IPV6 */
+
+
+ #ifdef WIN32
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     INTERFACE_INFO ii[64];
+     unsigned long length, i;
+     SOCKET sock;
+
+     sock = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+     if (sock == SOCKET_ERROR)
+         return -1;
+
+     if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, 0, 0, &ii,
+                  sizeof(ii), &length, 0, 0) == SOCKET_ERROR)
+     {
+         closesocket(sock);
+         return -1;
+     }
+
+     for (i = 0; i < length / sizeof (INTERFACE_INFO); ++i)
+         (callback)((struct sockaddr*)&ii[i].iiAddress,
+                    (struct sockaddr*)&ii[i].iiNetmask, cb_data);
+
+     closesocket(sock);
+     return 0;
+ }
+
+ #elif HAVE_GETIFADDRS /* && !WIN32 */
+
+ #include <ifaddrs.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifaddrs *ifa, *l;
+
+     if (getifaddrs(&ifa) < 0)
+         return -1;
+
+     for (l = ifa; l; l = l->ifa_next)
+     {
+         if (l->ifa_addr && l->ifa_netmask)
+             (callback)(l->ifa_addr, l->ifa_netmask, cb_data);
+     }
+
+     freeifaddrs(ifa);
+     return 0;
+ }
+
+ #else /* !HAVE_GETIFADDRS && !WIN32 */
+
+ #include <sys/ioctl.h>
+ #include <net/if.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifconf ifc;
+     struct ifreq addr, mask;
+     char buffer[10240];
+     int sock;
+     int i, total;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     ifc.ifc_buf = buffer;
+     ifc.ifc_len = sizeof(buffer);
+
+     if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
+     {
+         close(sock);
+         return -1;
+     }
+
+     total = ifc.ifc_len / sizeof(struct ifreq);
+
+     for (i = 0; i < total; ++i)
+     {
+         memset(&addr, 0, sizeof (addr));
+         memcpy(addr.ifr_name, ifc.ifc_req[i].ifr_name, sizeof(addr.ifr_name));
+         memset(&mask, 0, sizeof (mask));
+         memcpy(mask.ifr_name, ifc.ifc_req[i].ifr_name, sizeof(mask.ifr_name));
+
+         if (ioctl(sock, SIOCGIFADDR, &addr, sizeof(addr)) == 0 &&
+             ioctl(sock, SIOCGIFNETMASK, &mask, sizeof(mask)) == 0)
+             (callback)(&addr.ifr_addr, &mask.ifr_netmask, cb_data);
+     }
+
+     close (sock);
+     return 0;
+ }
+
+ #endif /* !HAVE_GETIFADDRS && !WIN32 */
+
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..65966da 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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 specify "samehost" to match any of the
+ # server's own IP addresses, or "samenet" to match any address in a subnet that
+ # the server belongs to.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index b538ee4..5193f38 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h
index 1934957..9bd562c 100644
*** a/src/include/libpq/ip.h
--- b/src/include/libpq/ip.h
*************** extern void pg_promote_v4_to_v6_mask(str
*** 47,50 ****
--- 47,56 ----
  #define IS_AF_UNIX(fam) (0)
  #endif

+ typedef void (*PgIfAddrCallback)(struct sockaddr * addr,
+                                  struct sockaddr * netmask,
+                                  void * cb_data);
+
+ extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data);
+
  #endif   /* IP_H */

diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 702971a..3482627 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_ip(SockAddr *raddr, struct sockadd
*** 530,538 ****
               raddr->addr.ss_family == AF_INET6)
      {
          /*
!          * 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.
           */
          struct sockaddr_storage addrcopy,
                      maskcopy;
--- 530,538 ----
               raddr->addr.ss_family == AF_INET6)
      {
          /*
!          * If we're listening on IPv6 but the file specifies an IPv4 address to
!          * match against, we promote the latter also to an IPv6 address before
!          * trying to match the client's address.
           */
          struct sockaddr_storage addrcopy,
                      maskcopy;

Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Magnus Hagander
Date:
On Mon, Sep 21, 2009 at 20:12, Stef Walter <stef-list@memberwebs.com> wrote:


<snip>
> Updated in attached patch.

This patch does not build on Windows, the error is:
ip.obj : error LNK2019: unresolved external symbol __imp__WSAIoctl@36 referencedin function _pg_foreach_ifaddr
ip.obj : error LNK2019: unresolved external symbol __imp__WSASocketA@24 referenc
ed in function _pg_foreach_ifaddr
.\Release\libpq\libpq.dll : fatal error LNK1120: 2 unresolved externals


I don't have time to investigate this further right now, so if
somebody else want to dig into why that is happening that would be
helpful :)


Also, one thought - with samenet we currently from what I can tell
enumerate all interfaces. Not just those we bind to based on
listen_addresses. Is that intentional, or should we restrict us to
subnets reachable through the interfaces we're actually listening on?


-- Magnus HaganderMe: http://www.hagander.net/Work: http://www.redpill-linpro.com/


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Magnus Hagander wrote:
> On Mon, Sep 21, 2009 at 20:12, Stef Walter <stef-list@memberwebs.com> wrote:
> 
> 
> <snip>
>> Updated in attached patch.
> 
> This patch does not build on Windows, the error is:
> ip.obj : error LNK2019: unresolved external symbol __imp__WSAIoctl@36 referenced
>  in function _pg_foreach_ifaddr
> ip.obj : error LNK2019: unresolved external symbol __imp__WSASocketA@24 referenc
> ed in function _pg_foreach_ifaddr
> .\Release\libpq\libpq.dll : fatal error LNK1120: 2 unresolved externals
> 
> 
> I don't have time to investigate this further right now, so if
> somebody else want to dig into why that is happening that would be
> helpful :)

My windows VM is giving me problems, but I'll try look into it unless
someone else beats me to do it.

> Also, one thought - with samenet we currently from what I can tell
> enumerate all interfaces. Not just those we bind to based on
> listen_addresses. Is that intentional, or should we restrict us to
> subnets reachable through the interfaces we're actually listening on?

This would change the scope of the patch significantly. It seems that
adding that limitation is unnecessary. In my opinion, if stricter hba
security is required, and limiting to specific subnets are desired,
those subnets should be entered directly into the pg_hba.conf file.

Currently people are adding 0.0.0.0 to a default pg_hba.conf file in
order to allow access from nearby machines, without running into the
maintenance problems of hard coding IP addresses. However using 0.0.0.0
is clearly suboptimal from a security perspective.

I've seen the samenet feature as a way to avoid the use of 0.0.0.0 in
these cases.

Obviously people who would like stricter postgres security can configure
subnets manually, and would probably not be comfortable with 'automatic'
decisions being made about the subnets allowed.

Cheers,

Stef



Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Magnus Hagander
Date:
On Wed, Sep 23, 2009 at 18:41, Stef Walter <stef-list@memberwebs.com> wrote:
> Magnus Hagander wrote:
>> On Mon, Sep 21, 2009 at 20:12, Stef Walter <stef-list@memberwebs.com> wrote:
>>
>>
>> <snip>
>>> Updated in attached patch.
>>
>> This patch does not build on Windows, the error is:
>> ip.obj : error LNK2019: unresolved external symbol __imp__WSAIoctl@36 referenced
>>  in function _pg_foreach_ifaddr
>> ip.obj : error LNK2019: unresolved external symbol __imp__WSASocketA@24 referenc
>> ed in function _pg_foreach_ifaddr
>> .\Release\libpq\libpq.dll : fatal error LNK1120: 2 unresolved externals
>>
>>
>> I don't have time to investigate this further right now, so if
>> somebody else want to dig into why that is happening that would be
>> helpful :)
>
> My windows VM is giving me problems, but I'll try look into it unless
> someone else beats me to do it.

If you want a VM that works, look at:
http://blog.hagander.net/archives/151-Testing-PostgreSQL-patches-on-Windows-using-Amazon-EC2.html

If it's just the VM... :-)

>> Also, one thought - with samenet we currently from what I can tell
>> enumerate all interfaces. Not just those we bind to based on
>> listen_addresses. Is that intentional, or should we restrict us to
>> subnets reachable through the interfaces we're actually listening on?
>
> This would change the scope of the patch significantly. It seems that
> adding that limitation is unnecessary. In my opinion, if stricter hba
> security is required, and limiting to specific subnets are desired,
> those subnets should be entered directly into the pg_hba.conf file.
>
> Currently people are adding 0.0.0.0 to a default pg_hba.conf file in
> order to allow access from nearby machines, without running into the
> maintenance problems of hard coding IP addresses. However using 0.0.0.0
> is clearly suboptimal from a security perspective.
>
> I've seen the samenet feature as a way to avoid the use of 0.0.0.0 in
> these cases.
>
> Obviously people who would like stricter postgres security can configure
> subnets manually, and would probably not be comfortable with 'automatic'
> decisions being made about the subnets allowed.

Agreed. In that case, I think we just need to make that clearer in the
docs, so people don't make the mistake of thinking it means somehting
other than what it does.


-- Magnus HaganderMe: http://www.hagander.net/Work: http://www.redpill-linpro.com/


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Robert Haas
Date:
On Wed, Sep 23, 2009 at 12:41 PM, Stef Walter <stef-list@memberwebs.com> wrote:
> Currently people are adding 0.0.0.0 to a default pg_hba.conf file in
> order to allow access from nearby machines, without running into the
> maintenance problems of hard coding IP addresses. However using 0.0.0.0
> is clearly suboptimal from a security perspective.

If people aren't willing to take the time (5 minutes?) to create an
hba.conf file that implements a reasonable security policy, I'm not
sure anything we can do - and certainly not this - is going to help
very much.  I haven't really looked at this patch, but how confident
are we that this is actually portable?  It would be a shame to spend a
lot of time and energy troubleshooting portability problems with a
feature that - IMO - has a fairly marginal use case to begin with.
...Robert


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Robert Haas wrote:
> On Wed, Sep 23, 2009 at 12:41 PM, Stef Walter <stef-list@memberwebs.com> wrote:
>> Currently people are adding 0.0.0.0 to a default pg_hba.conf file in
>> order to allow access from nearby machines, without running into the
>> maintenance problems of hard coding IP addresses. However using 0.0.0.0
>> is clearly suboptimal from a security perspective.
> 
> If people aren't willing to take the time (5 minutes?) to create an
> hba.conf file that implements a reasonable security policy, I'm not
> sure anything we can do - and certainly not this - is going to help
> very much.  I haven't really looked at this patch, but how confident
> are we that this is actually portable?  It would be a shame to spend a
> lot of time and energy troubleshooting portability problems with a
> feature that - IMO - has a fairly marginal use case to begin with.

Obviously this isn't the an authentication method. If you're using
'trust' authentication with anything but unix sockets you're pretty
screwed anyway. This is used in conjuction with an authentication method.

The core problem is with renumbering. Due to IPv4 addresses becoming
more and more scarce, ISPs are regularly foisting renumbering on their
customers. For example, it's in all the new contracts.

Often renumbering takes place on networks where the original developers
are long gone.

Postgresql has always been very fragile when renumbering due to hard
coded IP addresses in the pg_hba.conf file. This patch solves that
problem for most of the cases, where hosts nearby on the network can
talk to postgresql hosts without putting fragile rules into pg_hba.conf.

Allowing host names in pg_hba.conf would also solve this problem,
although the last person who tried to implement this it was a topic of
contention. I asked if I should focus on reverse DNS host names in
pg_hba.conf or portability for this samenet patch, and it was indicated
that I should do the latter.

If there is clear direction within the community to work on DNS based
stuff in pg_hba.conf I'd be willing to contribute effort there.

Cheers,

Stef



Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Robert Haas
Date:
On Wed, Sep 23, 2009 at 3:53 PM, Stef Walter <stef-list@memberwebs.com> wrote:
> Robert Haas wrote:
>> On Wed, Sep 23, 2009 at 12:41 PM, Stef Walter <stef-list@memberwebs.com> wrote:
>>> Currently people are adding 0.0.0.0 to a default pg_hba.conf file in
>>> order to allow access from nearby machines, without running into the
>>> maintenance problems of hard coding IP addresses. However using 0.0.0.0
>>> is clearly suboptimal from a security perspective.
>>
>> If people aren't willing to take the time (5 minutes?) to create an
>> hba.conf file that implements a reasonable security policy, I'm not
>> sure anything we can do - and certainly not this - is going to help
>> very much.  I haven't really looked at this patch, but how confident
>> are we that this is actually portable?  It would be a shame to spend a
>> lot of time and energy troubleshooting portability problems with a
>> feature that - IMO - has a fairly marginal use case to begin with.
>
> Obviously this isn't the an authentication method. If you're using
> 'trust' authentication with anything but unix sockets you're pretty
> screwed anyway. This is used in conjuction with an authentication method.
>
> The core problem is with renumbering. Due to IPv4 addresses becoming
> more and more scarce, ISPs are regularly foisting renumbering on their
> customers. For example, it's in all the new contracts.
>
> Often renumbering takes place on networks where the original developers
> are long gone.
>
> Postgresql has always been very fragile when renumbering due to hard
> coded IP addresses in the pg_hba.conf file. This patch solves that
> problem for most of the cases, where hosts nearby on the network can
> talk to postgresql hosts without putting fragile rules into pg_hba.conf.
>
> Allowing host names in pg_hba.conf would also solve this problem,
> although the last person who tried to implement this it was a topic of
> contention. I asked if I should focus on reverse DNS host names in
> pg_hba.conf or portability for this samenet patch, and it was indicated
> that I should do the latter.
>
> If there is clear direction within the community to work on DNS based
> stuff in pg_hba.conf I'd be willing to contribute effort there.

Personally, I can't imagine using any of these for anything that I
cared very much about.  IP renumberings are a pain, but I'd rather
take a little extra time to make sure it gets done right.  I have
other things that would need to be fixed too, besides PostgreSQL: for
example, IP tables rules.

That having been said, I don't think it's my place to harangue someone
else about their feature because it doesn't fit my use case.  But if
it's going to make PostgreSQL not compile/not work the same way on
platforms that we otherwise support, then I think it's a bad idea.
Otherwise I have no objection.

...Robert


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Tom Lane
Date:
Stef Walter <stef-list@memberwebs.com> writes:
> Allowing host names in pg_hba.conf would also solve this problem,
> although the last person who tried to implement this it was a topic of
> contention. I asked if I should focus on reverse DNS host names in
> pg_hba.conf or portability for this samenet patch, and it was indicated
> that I should do the latter.

Agreed, a DNS-based solution would be a huge pain in the rear to do
correctly.  However, I think what Robert wanted to know was just how
portable you believe this solution is.  If it doesn't work, and work
pretty much the same, on all our supported platforms then I'm afraid
we can't use it.  There's nothing worse than a security-critical
feature that works differently than you expect it to.

In this case what particularly scares me is the idea that 'samenet'
might be interpreted to let in a larger subnet than the user expected,
eg 10/8 instead of 10.0.0/24.  You'd likely not notice the problem until
after you'd been broken into ...
        regards, tom lane


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Mark Mielke
Date:
If looking for representation -

I consider the default pg_hba.conf to be problematic. Newbies start with 
"trust" access, and then do silly things to open it up.

I would use samehost, and if samenet worked the same way it does for 
Postfix, I would probably use samenet. This information can be pulled 
from the operating system, and the requirement for it to be hard-coded 
in pg_hba.conf is inconvenient at best, and problematic at worst. Yes, 
renumbering requires some thought - but I prefer applications that do 
the majority of this thought for me over applications that require me to 
do mundane activities.

I would also use DNS in pg_hba.conf if it were available. I can see some 
of the issues with this (should it be mapped to IP right away, or should 
it be re-evaluated every time?), but ultimately the feature would be 
useful, and would be widely used. Especially once we get to IPv6, 
specification of the addresses will become a horrible chore, and 
solutions which require the IPv6 address to be spelled out will be 
painful to use.

Both of these are generally one time costs for me. They are a pain, but 
most of us suck it up and swallow. It hasn't been on my list of itches 
that I just have to scratch. Remember, though, that the majority of 
PostgreSQL users are not represented on this list, and my pain here 
might be acceptable, but a newbie will probably either turn away or do 
something wrong. Better to give them a sensible configuration from the 
start from, and allow the experts to specify IP addresses if that is 
what they want to do.

Cheers,
mark

-- 
Mark Mielke<mark@mielke.cc>



Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Andrew Dunstan
Date:

Tom Lane wrote:
> In this case what particularly scares me is the idea that 'samenet'
> might be interpreted to let in a larger subnet than the user expected,
> eg 10/8 instead of 10.0.0/24.  You'd likely not notice the problem until
> after you'd been broken into ...
>
>   

I haven't looked at this "feature" at all, but I'd be inclined, on the 
grounds you quite reasonably cite, to require a netmask with "samenet", 
rather than just ask the interface for its netmask.

cheers

andrew


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Tom Lane
Date:
Andrew Dunstan <andrew@dunslane.net> writes:
> Tom Lane wrote:
>> In this case what particularly scares me is the idea that 'samenet'
>> might be interpreted to let in a larger subnet than the user expected,
>> eg 10/8 instead of 10.0.0/24.  You'd likely not notice the problem until
>> after you'd been broken into ...

> I haven't looked at this "feature" at all, but I'd be inclined, on the 
> grounds you quite reasonably cite, to require a netmask with "samenet", 
> rather than just ask the interface for its netmask.

I was just thinking the same thing.  Could we then unify samehost and
samenet into one thing?  sameaddr/24 or something like that, with
samehost just being the limiting case of all bits used.  I am not
sure though if this works nicely for IPv6 as well as IPv4.
        regards, tom lane


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Mark Mielke
Date:
On 09/23/2009 05:37 PM, Andrew Dunstan wrote:
> Tom Lane wrote:
>> In this case what particularly scares me is the idea that 'samenet'
>> might be interpreted to let in a larger subnet than the user expected,
>> eg 10/8 instead of 10.0.0/24.  You'd likely not notice the problem until
>> after you'd been broken into ...
>>
>
> I haven't looked at this "feature" at all, but I'd be inclined, on the 
> grounds you quite reasonably cite, to require a netmask with 
> "samenet", rather than just ask the interface for its netmask.

I think requiring a netmask defeats some of the value of samenet. When 
being assigned a new address can change subnet as well. For example, 
when we moved one of our machines from one room to another it went from 
/24 to /26.

I think it should be understood that the network will not work properly 
if the user has the wrong network configuration. If they accidentally 
use /8 instead of /24 on their interface - it's more likely that some or 
all of their network will become inaccessible, than somebody breaking 
into their machine. And, anything is better than 0.0.0.0.

There are two questions here I think - one is whether or not samenet is 
valid and would provide value, which I think it is and it does. A second 
question is whether it should be enabled in the default pg_hba.conf - I 
think not.

Postfix has this capability and it works fine. I use it to allow relay 
email from machines I "trust", because they are on my network. I think 
many people would use it, and it would be the right solution for many 
problems. Worrying about how some person somewhere might screw up, when 
they have the same opportunity to screw up if things are left unchanged 
(0.0.0.0) is not a practical way of looking at things.

How many Postfix servers have you heard of being open relays as a result 
of "samenet"? I haven't heard of it ever happening. I suppose it doesn't 
mean it hasn't happened - but I think getting the network interface 
configured properly being a necessity for the machine working properly 
is a very good encouragement for it to work.

Cheers,
mark

-- 
Mark Mielke<mark@mielke.cc>



Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Tom Lane
Date:
Mark Mielke <mark@mark.mielke.cc> writes:
> Postfix has this capability and it works fine.

Hmm, have we looked at the Postfix code to see exactly how they do it?
I'd be a *lot* more comfortable adopting logic that's been proven in the
field than something written from scratch.
        regards, tom lane


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Mark Mielke
Date:
On 09/23/2009 05:40 PM, Tom Lane wrote:
>> I haven't looked at this "feature" at all, but I'd be inclined, on the
>> grounds you quite reasonably cite, to require a netmask with "samenet",
>> rather than just ask the interface for its netmask.
>>      
> I was just thinking the same thing.  Could we then unify samehost and
> samenet into one thing?  sameaddr/24 or something like that, with
> samehost just being the limiting case of all bits used.  I am not
> sure though if this works nicely for IPv6 as well as IPv4.

I could see some people wanting this as well - but it's not a 
replacement for samenet, it would be an additional feature. For example, 
at my company, I have a cluster of machines on a /26 subnet, but for 
some accesses, I would prefer to "open it up" to /8, since our company 
has a /8, and I may want to allow anybody in the company to connect, 
regardless of how things are routed.

I may still want samenet in the same configuration, to grant additional 
access if the person happens to be on my switch compared to "anywhere in 
the company". For my switch, having to hard code the subnet is back to 
being a pain. If we enlarge our subnet to /25, it's one more thing that 
I would have to remember to change unnecessarily.

Cheers,
mark

-- 
Mark Mielke<mark@mielke.cc>



Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Tom Lane wrote:
> Mark Mielke <mark@mark.mielke.cc> writes:
>> Postfix has this capability and it works fine.
> 
> Hmm, have we looked at the Postfix code to see exactly how they do it?
> I'd be a *lot* more comfortable adopting logic that's been proven in the
> field than something written from scratch.

Good idea.

As far as I know postfix doesn't support win32. They use a similar
approach with using ioctls on some systems, getifaddrs on others.

I can take a look at the postfix code (src/util/inet_addr_local.c),
check out licenses, add win32 support and adapt it to postgres uses.

Cheers,

Stef



Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Tom Lane wrote:
> Stef Walter <stef-list@memberwebs.com> writes:
>> Allowing host names in pg_hba.conf would also solve this problem,
>> although the last person who tried to implement this it was a topic of
>> contention. I asked if I should focus on reverse DNS host names in
>> pg_hba.conf or portability for this samenet patch, and it was indicated
>> that I should do the latter.
> 
> Agreed, a DNS-based solution would be a huge pain in the rear to do
> correctly.  However, I think what Robert wanted to know was just how
> portable you believe this solution is.  If it doesn't work, and work
> pretty much the same, on all our supported platforms then I'm afraid
> we can't use it.  

It does work the same on the platforms noted earlier. After work today,
I'll put time into making sure that the winsock build problem noted
earlier is sorted out.

> In this case what particularly scares me is the idea that 'samenet'
> might be interpreted to let in a larger subnet than the user expected,
> eg 10/8 instead of 10.0.0/24.  You'd likely not notice the problem until
> after you'd been broken into ...

As Mark noted in another email, ones networking wouldn't work at all
with such a misconfiguration.

But if you like I can add additional defensive checks in the code to
ignore those obviously invalid netmasks like /0. Basically the OS would
be giving postgres bad information. Does postgres generally try to guard
against this? I'll follow the convention of the project.

Cheers,

Stef



Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Tom Lane
Date:
Stef Walter <stef-list@memberwebs.com> writes:
> But if you like I can add additional defensive checks in the code to
> ignore those obviously invalid netmasks like /0. Basically the OS would
> be giving postgres bad information. Does postgres generally try to guard
> against this? I'll follow the convention of the project.

I think we'd only bother with that if it was a known failure mode
due to platform bugs or common misconfigurations.
        regards, tom lane


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Robert Haas
Date:
On Wed, Sep 23, 2009 at 7:56 PM, Stef Walter <stef-list@memberwebs.com> wrote:
> Tom Lane wrote:
>> Stef Walter <stef-list@memberwebs.com> writes:
>>> Allowing host names in pg_hba.conf would also solve this problem,
>>> although the last person who tried to implement this it was a topic of
>>> contention. I asked if I should focus on reverse DNS host names in
>>> pg_hba.conf or portability for this samenet patch, and it was indicated
>>> that I should do the latter.
>>
>> Agreed, a DNS-based solution would be a huge pain in the rear to do
>> correctly.  However, I think what Robert wanted to know was just how
>> portable you believe this solution is.  If it doesn't work, and work
>> pretty much the same, on all our supported platforms then I'm afraid
>> we can't use it.
>
> It does work the same on the platforms noted earlier. After work today,
> I'll put time into making sure that the winsock build problem noted
> earlier is sorted out.

Rereading the thread, it seems that the main question is whether there
are any platforms that we support that have neither getifaddrs or
SIOCGIFCONF, or where they don't work properly.

By the way, in foreach_ifaddr_ifconf, what happens if the number of
addresses is too large to fit in the arbitrary-size buffer you've
chosen here?

...Robert


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Magnus Hagander wrote:
> On Mon, Sep 21, 2009 at 20:12, Stef Walter <stef-list@memberwebs.com> wrote:
> This patch does not build on Windows, the error is:
> ip.obj : error LNK2019: unresolved external symbol __imp__WSAIoctl@36 referenced
>  in function _pg_foreach_ifaddr
> ip.obj : error LNK2019: unresolved external symbol __imp__WSASocketA@24 referenc
> ed in function _pg_foreach_ifaddr
> .\Release\libpq\libpq.dll : fatal error LNK1120: 2 unresolved externals
>
>
> I don't have time to investigate this further right now, so if
> somebody else want to dig into why that is happening that would be
> helpful :)

Seems there are two windows build systems. Once I discovered the MSVC
one, and got it working, I added the required ws2 library (already used
by other components of postgresql).

Attached patch contains a fix.

Cheers,

Stef

diff --git a/configure.in b/configure.in
index e545a1f..b77ce2b 100644
*** a/configure.in
--- b/configure.in
*************** AC_SUBST(OSSP_UUID_LIBS)
*** 969,975 ****
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
--- 969,975 ----
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
*************** PGAC_VAR_INT_TIMEZONE
*** 1148,1154 ****
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
--- 1148,1154 ----
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index ad4d084..e5152f4 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 244,249 ****
--- 244,255 ----
         support for IPv6 addresses.
        </para>

+       <para>Instead of a <replaceable>CIDR-address</replaceable>, you can specify
+        <literal>samehost</literal> to match any of the server's own IP addresses,
+        or <literal>samenet</literal> to match any address in a subnet that the
+        server belongs to.
+       </para>
+
        <para>
         This field only applies to <literal>host</literal>,
         <literal>hostssl</literal>, and <literal>hostnossl</> records.
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index e6f7db2..702971a 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_db(const char *dbname, const char
*** 512,517 ****
--- 512,608 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ typedef struct CheckNetwork {
+     NetMethod method;
+     SockAddr *raddr;
+     bool result;
+ } CheckNetwork;
+
+ static void
+ callback_check_network(struct sockaddr *addr, struct sockaddr *netmask, void *data)
+ {
+     CheckNetwork *cn = data;
+     struct sockaddr_storage mask;
+
+     /* Already found a match */
+     if (cn->result)
+         return;
+
+     /* Make a fully 1's netmask of appropriate length */
+     if (cn->method == nmSameHost)
+     {
+         pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
+         cn->result = check_ip(cn->raddr, addr, (struct sockaddr*)&mask);
+     }
+
+     /* Use the netmask of the interface itself */
+     else
+     {
+         cn->result = check_ip(cn->raddr, addr, netmask);
+     }
+ }
+
+ static bool
+ check_same_host_or_net(SockAddr *raddr, NetMethod method)
+ {
+     CheckNetwork cn;
+     cn.method = method;
+     cn.raddr = raddr;
+     cn.result = false;
+
+     if (pg_foreach_ifaddr(callback_check_network, &cn) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Error enumerating network interfaces")));
+         return false;
+     }
+
+     return cn.result;
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 658,756 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 749,868 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp(token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp(token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1096,1131 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1208,1228 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr,
!                               (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!             case nmSameNet:
!                 if (!check_same_host_or_net(&port->raddr, hba->net_method))
                      continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..2aaab2e 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
*************** pg_promote_v4_to_v6_mask(struct sockaddr
*** 476,478 ****
--- 484,588 ----
  }

  #endif   /* HAVE_IPV6 */
+
+
+ #ifdef WIN32
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     INTERFACE_INFO ii[64];
+     unsigned long length, i;
+     SOCKET sock;
+
+     sock = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+     if (sock == SOCKET_ERROR)
+         return -1;
+
+     if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, 0, 0, &ii,
+                  sizeof(ii), &length, 0, 0) == SOCKET_ERROR)
+     {
+         closesocket(sock);
+         return -1;
+     }
+
+     for (i = 0; i < length / sizeof (INTERFACE_INFO); ++i)
+         (callback)((struct sockaddr*)&ii[i].iiAddress,
+                    (struct sockaddr*)&ii[i].iiNetmask, cb_data);
+
+     closesocket(sock);
+     return 0;
+ }
+
+ #elif HAVE_GETIFADDRS /* && !WIN32 */
+
+ #include <ifaddrs.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifaddrs *ifa, *l;
+
+     if (getifaddrs(&ifa) < 0)
+         return -1;
+
+     for (l = ifa; l; l = l->ifa_next)
+     {
+         if (l->ifa_addr && l->ifa_netmask)
+             (callback)(l->ifa_addr, l->ifa_netmask, cb_data);
+     }
+
+     freeifaddrs(ifa);
+     return 0;
+ }
+
+ #else /* !HAVE_GETIFADDRS && !WIN32 */
+
+ #include <sys/ioctl.h>
+ #include <net/if.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifconf ifc;
+     struct ifreq addr, mask;
+     char buffer[10240];
+     int sock;
+     int i, total;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     ifc.ifc_buf = buffer;
+     ifc.ifc_len = sizeof(buffer);
+
+     if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
+     {
+         close(sock);
+         return -1;
+     }
+
+     total = ifc.ifc_len / sizeof(struct ifreq);
+
+     for (i = 0; i < total; ++i)
+     {
+         memset(&addr, 0, sizeof (addr));
+         memcpy(addr.ifr_name, ifc.ifc_req[i].ifr_name, sizeof(addr.ifr_name));
+         memset(&mask, 0, sizeof (mask));
+         memcpy(mask.ifr_name, ifc.ifc_req[i].ifr_name, sizeof(mask.ifr_name));
+
+         if (ioctl(sock, SIOCGIFADDR, &addr, sizeof(addr)) == 0 &&
+             ioctl(sock, SIOCGIFNETMASK, &mask, sizeof(mask)) == 0)
+             (callback)(&addr.ifr_addr, &mask.ifr_netmask, cb_data);
+     }
+
+     close (sock);
+     return 0;
+ }
+
+ #endif /* !HAVE_GETIFADDRS && !WIN32 */
+
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..65966da 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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 specify "samehost" to match any of the
+ # server's own IP addresses, or "samenet" to match any address in a subnet that
+ # the server belongs to.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index b538ee4..5193f38 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h
index 1934957..9bd562c 100644
*** a/src/include/libpq/ip.h
--- b/src/include/libpq/ip.h
*************** extern void pg_promote_v4_to_v6_mask(str
*** 47,50 ****
--- 47,56 ----
  #define IS_AF_UNIX(fam) (0)
  #endif

+ typedef void (*PgIfAddrCallback)(struct sockaddr * addr,
+                                  struct sockaddr * netmask,
+                                  void * cb_data);
+
+ extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data);
+
  #endif   /* IP_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f5a01b3..2187420 100644
*** a/src/tools/msvc/Mkvcbuild.pm
--- b/src/tools/msvc/Mkvcbuild.pm
*************** sub mkvcbuild
*** 147,152 ****
--- 147,153 ----
      $libpq->AddIncludeDir('src\port');
      $libpq->AddLibrary('wsock32.lib');
      $libpq->AddLibrary('secur32.lib');
+     $libpq->AddLibrary('ws2_32.lib');
      $libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
      $libpq->UseDef('src\interfaces\libpq\libpqdll.def');
      $libpq->ReplaceFile('src\interfaces\libpq\libpqrc.c','src\interfaces\libpq\libpq.rc');

Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Robert Haas
Date:
On Thu, Sep 24, 2009 at 8:32 PM, Stef Walter <stef-list@memberwebs.com> wrote:
> Magnus Hagander wrote:
>> On Mon, Sep 21, 2009 at 20:12, Stef Walter <stef-list@memberwebs.com> wrote:
>> This patch does not build on Windows, the error is:
>> ip.obj : error LNK2019: unresolved external symbol __imp__WSAIoctl@36 referenced
>>  in function _pg_foreach_ifaddr
>> ip.obj : error LNK2019: unresolved external symbol __imp__WSASocketA@24 referenc
>> ed in function _pg_foreach_ifaddr
>> .\Release\libpq\libpq.dll : fatal error LNK1120: 2 unresolved externals
>>
>>
>> I don't have time to investigate this further right now, so if
>> somebody else want to dig into why that is happening that would be
>> helpful :)
>
> Seems there are two windows build systems. Once I discovered the MSVC
> one, and got it working, I added the required ws2 library (already used
> by other components of postgresql).
>
> Attached patch contains a fix.

So is this one Ready for Committer?

...Robert


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Robert Haas wrote:
>> Attached patch contains a fix.
> 
> So is this one Ready for Committer?

Not yet. Two more things to do. Will work on them early next week:
* On Solaris the ioctl used only returns IPv4 addresses.* Don't use hard coded buffers on win32 and ioctl.

Cheers,

Stef





Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Robert Haas wrote:
> So is this one Ready for Committer?

Here we go, I think this one is ready. In addition to previous patches,
it does:

 * Use some techniques from postfix for getting interface addresses.
   Couldn't use code outright, due to license incompatibilities.
 * Tested on Solaris, FreeBSD, Linux and Windows. As far as I can tell
   this should also work on Mac OS, HPUX and AIX, and probably others.
 * Added src/tools/ifaddrs/test_ifaddrs tool for testing interface
   address code.

Cheers,

Stef
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index ad4d084..e5152f4 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 244,249 ****
--- 244,255 ----
         support for IPv6 addresses.
        </para>

+       <para>Instead of a <replaceable>CIDR-address</replaceable>, you can specify
+        <literal>samehost</literal> to match any of the server's own IP addresses,
+        or <literal>samenet</literal> to match any address in a subnet that the
+        server belongs to.
+       </para>
+
        <para>
         This field only applies to <literal>host</literal>,
         <literal>hostssl</literal>, and <literal>hostnossl</> records.
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index e6f7db2..702971a 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_db(const char *dbname, const char
*** 512,517 ****
--- 512,608 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ typedef struct CheckNetwork {
+     NetMethod method;
+     SockAddr *raddr;
+     bool result;
+ } CheckNetwork;
+
+ static void
+ callback_check_network(struct sockaddr *addr, struct sockaddr *netmask, void *data)
+ {
+     CheckNetwork *cn = data;
+     struct sockaddr_storage mask;
+
+     /* Already found a match */
+     if (cn->result)
+         return;
+
+     /* Make a fully 1's netmask of appropriate length */
+     if (cn->method == nmSameHost)
+     {
+         pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
+         cn->result = check_ip(cn->raddr, addr, (struct sockaddr*)&mask);
+     }
+
+     /* Use the netmask of the interface itself */
+     else
+     {
+         cn->result = check_ip(cn->raddr, addr, netmask);
+     }
+ }
+
+ static bool
+ check_same_host_or_net(SockAddr *raddr, NetMethod method)
+ {
+     CheckNetwork cn;
+     cn.method = method;
+     cn.raddr = raddr;
+     cn.result = false;
+
+     if (pg_foreach_ifaddr(callback_check_network, &cn) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Error enumerating network interfaces")));
+         return false;
+     }
+
+     return cn.result;
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 658,756 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 749,868 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp(token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp(token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1096,1131 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1208,1228 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr,
!                               (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!             case nmSameNet:
!                 if (!check_same_host_or_net(&port->raddr, hba->net_method))
                      continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..f0b7411 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
*************** pg_promote_v4_to_v6_mask(struct sockaddr
*** 476,478 ****
--- 484,747 ----
  }

  #endif   /* HAVE_IPV6 */
+
+
+ static void
+ run_ifaddr_callback(PgIfAddrCallback callback, void * cb_data,
+                     struct sockaddr * addr, struct sockaddr * mask)
+ {
+     struct sockaddr_storage full;
+
+     if (!addr)
+         return;
+
+     /* Check that the mask is valid */
+     if (mask)
+     {
+         if (mask->sa_family != addr->sa_family)
+         {
+             mask = NULL;
+         }
+
+         else if (mask->sa_family == AF_INET)
+         {
+             if (((struct sockaddr_in*)mask)->sin_addr.s_addr == INADDR_ANY)
+                 mask = NULL;
+         }
+ #ifdef HAVE_IPV6
+         else if (mask->sa_family == AF_INET6)
+         {
+             if (IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)mask)->sin6_addr))
+                 mask = NULL;
+         }
+ #endif
+     }
+
+     /* If mask is invalid, generate our own fully set mask */
+     if (!mask)
+     {
+         pg_sockaddr_cidr_mask(&full, NULL, addr->sa_family);
+         mask = (struct sockaddr*)&full;
+     }
+
+     (callback) (addr, mask, cb_data);
+ }
+
+ /*
+  * Enumerate network interface addresses on Win32. This uses
+  * the Winsock 2 functions (ie: ws2_32.dll)
+  */
+ #ifdef WIN32
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     INTERFACE_INFO ii[64];
+     unsigned long length, i;
+     SOCKET sock;
+
+     sock = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+     if (sock == SOCKET_ERROR)
+         return -1;
+
+     if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, 0, 0, &ii,
+                  sizeof(ii), &length, 0, 0) == SOCKET_ERROR)
+     {
+         closesocket(sock);
+         return -1;
+     }
+
+     for (i = 0; i < length / sizeof (INTERFACE_INFO); ++i)
+         run_ifaddr_callback (callback, cb_data,
+                              (struct sockaddr*)&ii[i].iiAddress,
+                              (struct sockaddr*)&ii[i].iiNetmask);
+
+     closesocket(sock);
+     return 0;
+ }
+
+ /*
+  * Enumerate interfaces on BSDs, AIX and modern Linux.
+  * Uses the getifaddrs() interface, clean and simple.
+  */
+
+ #elif HAVE_GETIFADDRS /* && !WIN32 */
+
+ #include <ifaddrs.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifaddrs *ifa, *l;
+
+     if (getifaddrs(&ifa) < 0)
+         return -1;
+
+     for (l = ifa; l; l = l->ifa_next)
+         run_ifaddr_callback (callback, cb_data,
+                              l->ifa_addr, l->ifa_netmask);
+
+     freeifaddrs(ifa);
+     return 0;
+ }
+
+ #else /* !HAVE_GETIFADDRS && !WIN32 */
+
+ #include <sys/ioctl.h>
+ #include <net/if.h>
+
+ #ifdef HAVE_SYS_SOCKIO_H
+ #include <sys/sockio.h>
+ #endif
+
+ /*
+  * SIOCGIFCONF does not return IPv6 addresses on Solaris,
+  * and HP/UX. So we use SIOCGLIFCONF if it's available.
+  */
+
+ #ifdef SIOCGLIFCONF
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void *cb_data)
+ {
+     struct lifconf lifc;
+     struct lifreq *lifr, lmask;
+     struct sockaddr *addr, *mask;
+     char buffer[10240];
+     int sock, fd;
+ #ifdef HAVE_IPV6
+     int sock6;
+ #endif
+     int i, total;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+ #ifdef HAVE_IPV6
+     sock6 = socket(AF_INET6, SOCK_DGRAM, 0);
+     if (sock6 == -1)
+     {
+         close(sock);
+         return -1;
+     }
+ #endif
+
+     memset(&lifc, 0, sizeof (lifc));
+     lifc.lifc_family = AF_UNSPEC;
+     lifc.lifc_len = sizeof (buffer);
+     lifc.lifc_buf = buffer;
+
+     if (ioctl(sock, SIOCGLIFCONF, &lifc) < 0)
+     {
+         close(sock);
+ #ifdef HAVE_IPV6
+         close(sock6);
+ #endif
+         return -1;
+     }
+
+     total = lifc.lifc_len / sizeof(struct lifreq);
+     lifr = lifc.lifc_req;
+     for (i = 0; i < total; ++i)
+     {
+         addr = (struct sockaddr*)&lifr[i].lifr_addr;
+         memcpy(&lmask, &lifr[i], sizeof (struct lifreq));
+ #ifdef HAVE_IPV6
+         fd = addr->sa_family == AF_INET6 ? sock6 : sock;
+ #else
+         fd = sock;
+ #endif
+         if (ioctl(fd, SIOCGLIFNETMASK, &lmask) < 0)
+             mask = NULL;
+         else
+             mask = (struct sockaddr*)&lmask.lifr_addr;
+         run_ifaddr_callback (callback, cb_data, addr, mask);
+     }
+
+     close(sock);
+ #ifdef HAVE_IPV6
+     close(sock6);
+ #endif
+     return 0;
+ }
+
+ /*
+  * Remaining Unixes use SIOCGIFCONF. Some only return IPv4 information
+  * here, so this is the least preferred method. Note that there is no
+  * standard way to iterate the struct ifreq returned in the array.
+  * On some OSs the structures are padded large enough for any address,
+  * on others you have to calculate the size of the struct ifreq.
+  */
+
+ #elif defined(SIOCGIFCONF)
+
+ /* Some OSs have _SIZEOF_ADDR_IFREQ, so just use that */
+ #ifndef _SIZEOF_ADDR_IFREQ
+
+ /* Calculate based on sockaddr.sa_len */
+ #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ #define _SIZEOF_ADDR_IFREQ(ifr) \
+         ((ifr).ifr_addr.sa_len > sizeof(struct sockaddr) ? \
+          (sizeof(struct ifreq) - sizeof(struct sockaddr) + \
+           (ifr).ifr_addr.sa_len) : sizeof(struct ifreq))
+
+ /* Padded ifreq structure, simple */
+ #else
+ #define _SIZEOF_ADDR_IFREQ(ifr) \
+     sizeof (struct ifreq)
+
+ #endif
+ #endif
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifconf ifc;
+     struct ifreq *ifr, *end, addr, mask;
+     char buffer[10240];
+     int sock;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     ifc.ifc_buf = buffer;
+     ifc.ifc_len = sizeof(buffer);
+
+     if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
+     {
+         close(sock);
+         return -1;
+     }
+
+     end = (struct ifreq*)(buffer + ifc.ifc_len);
+     for (ifr = ifc.ifc_req; ifr < end;)
+     {
+         memcpy(&addr, ifr, sizeof (addr));
+         memcpy(&mask, ifr, sizeof (mask));
+         if (ioctl(sock, SIOCGIFADDR, &addr, sizeof(addr)) == 0 &&
+             ioctl(sock, SIOCGIFNETMASK, &mask, sizeof(mask)) == 0)
+             run_ifaddr_callback(callback, cb_data,
+                                 &addr.ifr_addr, &mask.ifr_addr);
+         ifr = (struct ifreq*)((char*)ifr + _SIZEOF_ADDR_IFREQ(*ifr));
+     }
+
+     close(sock);
+     return 0;
+ }
+
+ #else /* !defined(SIOCGIFCONF) */
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     return -1;
+ }
+
+ #endif /* !defined(SIOCGIFCONF) */
+
+ #endif /* !HAVE_GETIFADDRS */
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..65966da 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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 specify "samehost" to match any of the
+ # server's own IP addresses, or "samenet" to match any address in a subnet that
+ # the server belongs to.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index b538ee4..5193f38 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h
index 1934957..9bd562c 100644
*** a/src/include/libpq/ip.h
--- b/src/include/libpq/ip.h
*************** extern void pg_promote_v4_to_v6_mask(str
*** 47,50 ****
--- 47,56 ----
  #define IS_AF_UNIX(fam) (0)
  #endif

+ typedef void (*PgIfAddrCallback)(struct sockaddr * addr,
+                                  struct sockaddr * netmask,
+                                  void * cb_data);
+
+ extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data);
+
  #endif   /* IP_H */
diff --git a/src/tools/ifaddrs/Makefile b/src/tools/ifaddrs/Makefile
index ...4ec26c1 .
*** a/src/tools/ifaddrs/Makefile
--- b/src/tools/ifaddrs/Makefile
***************
*** 0 ****
--- 1,26 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile for src/tools/ifaddrs
+ #
+ # Copyright (c) 2003-2009, PostgreSQL Global Development Group
+ #
+ # $PostgreSQL$
+ #
+ #-------------------------------------------------------------------------
+
+ subdir = src/tools/ifaddrs
+ top_builddir = ../../..
+ include $(top_builddir)/src/Makefile.global
+
+ libpq_backdir = $(top_builddir)/src/backend/libpq/
+ override CPPFLAGS := -I$(libpq_backdir) $(CPPFLAGS)
+
+ OBJS= test_ifaddrs.o
+
+ all: test_ifaddrs
+
+ test_ifaddrs: test_ifaddrs.o $(libpq_backdir)/ip.o
+     $(CC) $(CFLAGS) test_ifaddrs.o $(libpq_backdir)/ip.o $(LDFLAGS) $(LIBS) -o $@$(X)
+
+ clean distclean maintainer-clean:
+     rm -f test_ifaddrs$(X) $(OBJS)
diff --git a/src/tools/ifaddrs/README b/src/tools/ifaddrs/README
index ...98e84ff .
*** a/src/tools/ifaddrs/README
--- b/src/tools/ifaddrs/README
***************
*** 0 ****
--- 1,10 ----
+ $PostgreSQL$
+
+ ifaddrs
+ =======
+
+ This program looks up all the IPv4 and IPv6 interfaces on the local machine. It is useful for testing that this
functionalityworks on various platforms. 
+
+     Usage:    test_ifaddrs
+
+
diff --git a/src/tools/ifaddrs/test_ifaddrs.c b/src/tools/ifaddrs/test_ifaddrs.c
index ...4e77cbd .
*** a/src/tools/ifaddrs/test_ifaddrs.c
--- b/src/tools/ifaddrs/test_ifaddrs.c
***************
*** 0 ****
--- 1,72 ----
+ /*
+  * $PostgreSQL$
+  *
+  *
+  *    test_ifaddrs.c
+  *        test pg_foreach_ifaddr()
+  */
+
+ #include "postgres.h"
+
+ #include "libpq/ip.h"
+
+ #include <sys/types.h>
+ #include <sys/socket.h>
+
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
+
+ static void
+ print_addr(struct sockaddr *addr)
+ {
+     char buffer[256];
+     int ret, len;
+
+     switch (addr->sa_family)
+     {
+     case AF_INET:
+         len = sizeof (struct sockaddr_in);
+         break;
+     case AF_INET6:
+         len = sizeof (struct sockaddr_in6);
+         break;
+     default:
+         len = sizeof (struct sockaddr_storage);
+         break;
+     }
+
+     ret = getnameinfo(addr, len, buffer, 256, NULL, 0, NI_NUMERICHOST);
+     if (ret != 0)
+         printf ("[unknown:%d]", addr->sa_family);
+     else
+         printf ("%s", buffer);
+ }
+
+ static void
+ callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
+ {
+     printf ("addr: ");
+     print_addr (addr);
+     printf ("  mask: ");
+     print_addr (mask);
+     printf ("\n");
+ }
+
+ int
+ main(int argc, char *argv[])
+ {
+ #ifdef WIN32
+     WSADATA wsaData;
+     if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+         fprintf (stderr, "WSAStartup failed\n");
+     else
+ #endif
+     if (pg_foreach_ifaddr (callback, NULL) < 0)
+         fprintf (stderr, "pg_foreach_ifaddr failed: %s\n", strerror (errno));
+     return 0;
+ }
+
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f5a01b3..2187420 100644
*** a/src/tools/msvc/Mkvcbuild.pm
--- b/src/tools/msvc/Mkvcbuild.pm
*************** sub mkvcbuild
*** 147,152 ****
--- 147,153 ----
      $libpq->AddIncludeDir('src\port');
      $libpq->AddLibrary('wsock32.lib');
      $libpq->AddLibrary('secur32.lib');
+     $libpq->AddLibrary('ws2_32.lib');
      $libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
      $libpq->UseDef('src\interfaces\libpq\libpqdll.def');
      $libpq->ReplaceFile('src\interfaces\libpq\libpqrc.c','src\interfaces\libpq\libpq.rc');

Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Whoops I missed this email...

Robert Haas wrote:
> Rereading the thread, it seems that the main question is whether there
> are any platforms that we support that have neither getifaddrs or
> SIOCGIFCONF, or where they don't work properly.

As far as I can tell, there are no non-ancient mainstream platforms that
we're missing here. As Tom suggested, I've looked over postfix, bind and
pcap and merged what I've learned into the (attached) samenet patch. I
believe we're hitting all the majors here:

 * Win32 using win_wsa2.dll
 * Modern versions of: Linux, BSD, Mac OS X, AIX using getifaddrs
 * Modern Solaris and HPUX using ioctl/SIOCGLIFCONF
 * Older unixes (BSD, Linux, Solaris, AIX) using ioctl/SIOCGIFCONF

SIOCGIFCONF doesn't return IPv6 information on certain platforms (such
as modern Solaris, or older Linux).

I believe we're covering every single Unix in use out there. I have
however only verified this assertion on open source OS's. I've also
verified that the SIOCGIFCONF method on Linux, BSD and Solaris, even
though they use more modern methods by default.

If a problem occurs with this code the src/tools/ifaddrs tool can be
used to diagnose the problem, and send in debugging feedback.

> By the way, in foreach_ifaddr_ifconf, what happens if the number of
> addresses is too large to fit in the arbitrary-size buffer you've
> chosen here?

The old approach was not a security vulnerability, and I find it hard to
believe that anyone would have had more than 10K of addresses. However
for the sake of completeness attached is a patch with dynamically sized
buffers. This adds some code complexity, but maybe someone out there
would have run into this (extremely) edge case.

I believe this patch to be complete, and am looking forward to review.

Cheers,

Stef

diff --git a/configure.in b/configure.in
index e545a1f..5182714 100644
*** a/configure.in
--- b/configure.in
*************** AC_SUBST(OSSP_UUID_LIBS)
*** 969,975 ****
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
--- 969,975 ----
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h sys/sockio.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
*************** PGAC_VAR_INT_TIMEZONE
*** 1148,1154 ****
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
--- 1148,1154 ----
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index ad4d084..e5152f4 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 244,249 ****
--- 244,255 ----
         support for IPv6 addresses.
        </para>

+       <para>Instead of a <replaceable>CIDR-address</replaceable>, you can specify
+        <literal>samehost</literal> to match any of the server's own IP addresses,
+        or <literal>samenet</literal> to match any address in a subnet that the
+        server belongs to.
+       </para>
+
        <para>
         This field only applies to <literal>host</literal>,
         <literal>hostssl</literal>, and <literal>hostnossl</> records.
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index e6f7db2..702971a 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_db(const char *dbname, const char
*** 512,517 ****
--- 512,608 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ typedef struct CheckNetwork {
+     NetMethod method;
+     SockAddr *raddr;
+     bool result;
+ } CheckNetwork;
+
+ static void
+ callback_check_network(struct sockaddr *addr, struct sockaddr *netmask, void *data)
+ {
+     CheckNetwork *cn = data;
+     struct sockaddr_storage mask;
+
+     /* Already found a match */
+     if (cn->result)
+         return;
+
+     /* Make a fully 1's netmask of appropriate length */
+     if (cn->method == nmSameHost)
+     {
+         pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
+         cn->result = check_ip(cn->raddr, addr, (struct sockaddr*)&mask);
+     }
+
+     /* Use the netmask of the interface itself */
+     else
+     {
+         cn->result = check_ip(cn->raddr, addr, netmask);
+     }
+ }
+
+ static bool
+ check_same_host_or_net(SockAddr *raddr, NetMethod method)
+ {
+     CheckNetwork cn;
+     cn.method = method;
+     cn.raddr = raddr;
+     cn.result = false;
+
+     if (pg_foreach_ifaddr(callback_check_network, &cn) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Error enumerating network interfaces")));
+         return false;
+     }
+
+     return cn.result;
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 658,756 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 749,868 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp(token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp(token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1096,1131 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1208,1228 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr,
!                               (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!             case nmSameNet:
!                 if (!check_same_host_or_net(&port->raddr, hba->net_method))
                      continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..25098a8 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
*************** pg_promote_v4_to_v6_mask(struct sockaddr
*** 476,478 ****
--- 484,820 ----
  }

  #endif   /* HAVE_IPV6 */
+
+
+ static void
+ run_ifaddr_callback(PgIfAddrCallback callback, void * cb_data,
+                     struct sockaddr * addr, struct sockaddr * mask)
+ {
+     struct sockaddr_storage full;
+
+     if (!addr)
+         return;
+
+     /* Check that the mask is valid */
+     if (mask)
+     {
+         if (mask->sa_family != addr->sa_family)
+         {
+             mask = NULL;
+         }
+
+         else if (mask->sa_family == AF_INET)
+         {
+             if (((struct sockaddr_in*)mask)->sin_addr.s_addr == INADDR_ANY)
+                 mask = NULL;
+         }
+ #ifdef HAVE_IPV6
+         else if (mask->sa_family == AF_INET6)
+         {
+             if (IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)mask)->sin6_addr))
+                 mask = NULL;
+         }
+ #endif
+     }
+
+     /* If mask is invalid, generate our own fully set mask */
+     if (!mask)
+     {
+         pg_sockaddr_cidr_mask(&full, NULL, addr->sa_family);
+         mask = (struct sockaddr*)&full;
+     }
+
+     (callback) (addr, mask, cb_data);
+ }
+
+ /*
+  * Enumerate network interface addresses on Win32. This uses
+  * the Winsock 2 functions (ie: ws2_32.dll)
+  */
+ #ifdef WIN32
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     INTERFACE_INFO *ptr, *ii = NULL;
+     unsigned long length, i;
+     unsigned long n_ii = 0;
+     SOCKET sock;
+     int error;
+
+     sock = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+     if (sock == SOCKET_ERROR)
+         return -1;
+
+     while (n_ii < 1024)
+     {
+         n_ii += 64;
+         ptr = realloc(ii, sizeof (INTERFACE_INFO) * n_ii);
+         if (!ptr)
+         {
+             free(ii);
+             closesocket(sock);
+             errno = ENOMEM;
+             return -1;
+         }
+
+         ii = ptr;
+         if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, 0, 0,
+                      ii, n_ii * sizeof (INTERFACE_INFO),
+                      &length, 0, 0) == SOCKET_ERROR)
+         {
+             error = WSAGetLastError();
+             if (error == WSAEFAULT || error == WSAENOBUFS)
+                 continue;
+             closesocket(sock);
+             free(ii);
+             return -1;
+         }
+
+         break;
+     }
+
+     for (i = 0; i < length / sizeof (INTERFACE_INFO); ++i)
+         run_ifaddr_callback (callback, cb_data,
+                              (struct sockaddr*)&ii[i].iiAddress,
+                              (struct sockaddr*)&ii[i].iiNetmask);
+
+     closesocket(sock);
+     free(ii);
+     return 0;
+ }
+
+ /*
+  * Enumerate interfaces on BSDs, AIX and modern Linux.
+  * Uses the getifaddrs() interface, clean and simple.
+  */
+
+ #elif HAVE_GETIFADDRS /* && !WIN32 */
+
+ #include <ifaddrs.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifaddrs *ifa, *l;
+
+     if (getifaddrs(&ifa) < 0)
+         return -1;
+
+     for (l = ifa; l; l = l->ifa_next)
+         run_ifaddr_callback (callback, cb_data,
+                              l->ifa_addr, l->ifa_netmask);
+
+     freeifaddrs(ifa);
+     return 0;
+ }
+
+ #else /* !HAVE_GETIFADDRS && !WIN32 */
+
+ #include <sys/ioctl.h>
+ #include <net/if.h>
+
+ #ifdef HAVE_SYS_SOCKIO_H
+ #include <sys/sockio.h>
+ #endif
+
+ /*
+  * SIOCGIFCONF does not return IPv6 addresses on Solaris,
+  * and HP/UX. So we use SIOCGLIFCONF if it's available.
+  */
+
+ #ifdef SIOCGLIFCONF
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void *cb_data)
+ {
+     struct lifconf lifc;
+     struct lifreq *lifr, lmask;
+     struct sockaddr *addr, *mask;
+     char *ptr, *buffer = NULL;
+     size_t n_buffer = 1024;
+     int sock, fd;
+ #ifdef HAVE_IPV6
+     int sock6;
+ #endif
+     int i, total;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     while(n_buffer < 1024 * 100)
+     {
+         n_buffer += 1024;
+         ptr = realloc(buffer, n_buffer);
+         if (!ptr)
+         {
+             free(buffer);
+             close(sock);
+             errno = ENOMEM;
+             return -1;
+         }
+
+         memset(&lifc, 0, sizeof (lifc));
+         lifc.lifc_family = AF_UNSPEC;
+         lifc.lifc_len = n_buffer;
+         lifc.lifc_buf = buffer = ptr;
+
+         if (ioctl(sock, SIOCGLIFCONF, &lifc) < 0)
+         {
+             if (errno == EINVAL)
+                 continue;
+             free(buffer);
+             close(sock);
+             return -1;
+         }
+
+         /*
+          * Some Unixes try to return as much data as possible,
+          * no indication of whether enough space allocated.
+          */
+         if (lifc.lifc_len < n_buffer - 1024)
+             break;
+     }
+
+ #ifdef HAVE_IPV6
+     sock6 = socket(AF_INET6, SOCK_DGRAM, 0);
+     if (sock6 == -1)
+     {
+         free(buffer);
+         close(sock);
+         return -1;
+     }
+ #endif
+
+     total = lifc.lifc_len / sizeof(struct lifreq);
+     lifr = lifc.lifc_req;
+     for (i = 0; i < total; ++i)
+     {
+         addr = (struct sockaddr*)&lifr[i].lifr_addr;
+         memcpy(&lmask, &lifr[i], sizeof (struct lifreq));
+ #ifdef HAVE_IPV6
+         fd = addr->sa_family == AF_INET6 ? sock6 : sock;
+ #else
+         fd = sock;
+ #endif
+         if (ioctl(fd, SIOCGLIFNETMASK, &lmask) < 0)
+             mask = NULL;
+         else
+             mask = (struct sockaddr*)&lmask.lifr_addr;
+         run_ifaddr_callback (callback, cb_data, addr, mask);
+     }
+
+     free(buffer);
+     close(sock);
+ #ifdef HAVE_IPV6
+     close(sock6);
+ #endif
+     return 0;
+ }
+
+ /*
+  * Remaining Unixes use SIOCGIFCONF. Some only return IPv4 information
+  * here, so this is the least preferred method. Note that there is no
+  * standard way to iterate the struct ifreq returned in the array.
+  * On some OSs the structures are padded large enough for any address,
+  * on others you have to calculate the size of the struct ifreq.
+  */
+
+ #elif defined(SIOCGIFCONF)
+
+ /* Some OSs have _SIZEOF_ADDR_IFREQ, so just use that */
+ #ifndef _SIZEOF_ADDR_IFREQ
+
+ /* Calculate based on sockaddr.sa_len */
+ #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ #define _SIZEOF_ADDR_IFREQ(ifr) \
+         ((ifr).ifr_addr.sa_len > sizeof(struct sockaddr) ? \
+          (sizeof(struct ifreq) - sizeof(struct sockaddr) + \
+           (ifr).ifr_addr.sa_len) : sizeof(struct ifreq))
+
+ /* Padded ifreq structure, simple */
+ #else
+ #define _SIZEOF_ADDR_IFREQ(ifr) \
+     sizeof (struct ifreq)
+
+ #endif
+ #endif
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifconf ifc;
+     struct ifreq *ifr, *end, addr, mask;
+     char *ptr, *buffer = NULL;
+     size_t n_buffer = 1024;
+     int sock;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     while(n_buffer < 1024 * 100)
+     {
+         n_buffer += 1024;
+         ptr = realloc(buffer, n_buffer);
+         if (!ptr)
+         {
+             free(buffer);
+             close(sock);
+             errno = ENOMEM;
+             return -1;
+         }
+
+         memset(&ifc, 0, sizeof (ifc));
+         ifc.ifc_buf = buffer = ptr;
+         ifc.ifc_len = n_buffer;
+
+         if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
+         {
+             if (errno == EINVAL)
+                 continue;
+             free(buffer);
+             close(sock);
+             return -1;
+         }
+
+         /*
+          * Some Unixes try to return as much data as possible,
+          * no indication of whether enough space allocated.
+          */
+         if (ifc.ifc_len < n_buffer - 1024)
+             break;
+     }
+
+     end = (struct ifreq*)(buffer + ifc.ifc_len);
+     for (ifr = ifc.ifc_req; ifr < end;)
+     {
+         memcpy(&addr, ifr, sizeof (addr));
+         memcpy(&mask, ifr, sizeof (mask));
+         if (ioctl(sock, SIOCGIFADDR, &addr, sizeof(addr)) == 0 &&
+             ioctl(sock, SIOCGIFNETMASK, &mask, sizeof(mask)) == 0)
+             run_ifaddr_callback(callback, cb_data,
+                                 &addr.ifr_addr, &mask.ifr_addr);
+         ifr = (struct ifreq*)((char*)ifr + _SIZEOF_ADDR_IFREQ(*ifr));
+     }
+
+     free(buffer);
+     close(sock);
+     return 0;
+ }
+
+ #else /* !defined(SIOCGIFCONF) */
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     return -1;
+ }
+
+ #endif /* !defined(SIOCGIFCONF) */
+
+ #endif /* !HAVE_GETIFADDRS */
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..65966da 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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 specify "samehost" to match any of the
+ # server's own IP addresses, or "samenet" to match any address in a subnet that
+ # the server belongs to.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index b538ee4..5193f38 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h
index 1934957..9bd562c 100644
*** a/src/include/libpq/ip.h
--- b/src/include/libpq/ip.h
*************** extern void pg_promote_v4_to_v6_mask(str
*** 47,50 ****
--- 47,56 ----
  #define IS_AF_UNIX(fam) (0)
  #endif

+ typedef void (*PgIfAddrCallback)(struct sockaddr * addr,
+                                  struct sockaddr * netmask,
+                                  void * cb_data);
+
+ extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data);
+
  #endif   /* IP_H */
diff --git a/src/tools/ifaddrs/Makefile b/src/tools/ifaddrs/Makefile
index ...4ec26c1 .
*** a/src/tools/ifaddrs/Makefile
--- b/src/tools/ifaddrs/Makefile
***************
*** 0 ****
--- 1,26 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile for src/tools/ifaddrs
+ #
+ # Copyright (c) 2003-2009, PostgreSQL Global Development Group
+ #
+ # $PostgreSQL$
+ #
+ #-------------------------------------------------------------------------
+
+ subdir = src/tools/ifaddrs
+ top_builddir = ../../..
+ include $(top_builddir)/src/Makefile.global
+
+ libpq_backdir = $(top_builddir)/src/backend/libpq/
+ override CPPFLAGS := -I$(libpq_backdir) $(CPPFLAGS)
+
+ OBJS= test_ifaddrs.o
+
+ all: test_ifaddrs
+
+ test_ifaddrs: test_ifaddrs.o $(libpq_backdir)/ip.o
+     $(CC) $(CFLAGS) test_ifaddrs.o $(libpq_backdir)/ip.o $(LDFLAGS) $(LIBS) -o $@$(X)
+
+ clean distclean maintainer-clean:
+     rm -f test_ifaddrs$(X) $(OBJS)
diff --git a/src/tools/ifaddrs/README b/src/tools/ifaddrs/README
index ...98e84ff .
*** a/src/tools/ifaddrs/README
--- b/src/tools/ifaddrs/README
***************
*** 0 ****
--- 1,10 ----
+ $PostgreSQL$
+
+ ifaddrs
+ =======
+
+ This program looks up all the IPv4 and IPv6 interfaces on the local machine. It is useful for testing that this
functionalityworks on various platforms. 
+
+     Usage:    test_ifaddrs
+
+
diff --git a/src/tools/ifaddrs/test_ifaddrs.c b/src/tools/ifaddrs/test_ifaddrs.c
index ...4e77cbd .
*** a/src/tools/ifaddrs/test_ifaddrs.c
--- b/src/tools/ifaddrs/test_ifaddrs.c
***************
*** 0 ****
--- 1,72 ----
+ /*
+  * $PostgreSQL$
+  *
+  *
+  *    test_ifaddrs.c
+  *        test pg_foreach_ifaddr()
+  */
+
+ #include "postgres.h"
+
+ #include "libpq/ip.h"
+
+ #include <sys/types.h>
+ #include <sys/socket.h>
+
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
+
+ static void
+ print_addr(struct sockaddr *addr)
+ {
+     char buffer[256];
+     int ret, len;
+
+     switch (addr->sa_family)
+     {
+     case AF_INET:
+         len = sizeof (struct sockaddr_in);
+         break;
+     case AF_INET6:
+         len = sizeof (struct sockaddr_in6);
+         break;
+     default:
+         len = sizeof (struct sockaddr_storage);
+         break;
+     }
+
+     ret = getnameinfo(addr, len, buffer, 256, NULL, 0, NI_NUMERICHOST);
+     if (ret != 0)
+         printf ("[unknown:%d]", addr->sa_family);
+     else
+         printf ("%s", buffer);
+ }
+
+ static void
+ callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
+ {
+     printf ("addr: ");
+     print_addr (addr);
+     printf ("  mask: ");
+     print_addr (mask);
+     printf ("\n");
+ }
+
+ int
+ main(int argc, char *argv[])
+ {
+ #ifdef WIN32
+     WSADATA wsaData;
+     if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+         fprintf (stderr, "WSAStartup failed\n");
+     else
+ #endif
+     if (pg_foreach_ifaddr (callback, NULL) < 0)
+         fprintf (stderr, "pg_foreach_ifaddr failed: %s\n", strerror (errno));
+     return 0;
+ }
+
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f5a01b3..2187420 100644
*** a/src/tools/msvc/Mkvcbuild.pm
--- b/src/tools/msvc/Mkvcbuild.pm
*************** sub mkvcbuild
*** 147,152 ****
--- 147,153 ----
      $libpq->AddIncludeDir('src\port');
      $libpq->AddLibrary('wsock32.lib');
      $libpq->AddLibrary('secur32.lib');
+     $libpq->AddLibrary('ws2_32.lib');
      $libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
      $libpq->UseDef('src\interfaces\libpq\libpqdll.def');
      $libpq->ReplaceFile('src\interfaces\libpq\libpqrc.c','src\interfaces\libpq\libpq.rc');

Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Dave Page
Date:
On Mon, Sep 28, 2009 at 10:10 PM, Stef Walter <stef-list@memberwebs.com> wrote:

>  * Win32 using win_wsa2.dll

I assume you mean ws2_32.dll?


--
Dave Page
EnterpriseDB UK:   http://www.enterprisedb.com


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Dave Page wrote:
> On Mon, Sep 28, 2009 at 10:10 PM, Stef Walter <stef-list@memberwebs.com> wrote:
> 
>>  * Win32 using win_wsa2.dll
> 
> I assume you mean ws2_32.dll?

Yes. I get dyslexic around windows DLLs. :)

Stef


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Robert Haas
Date:
On Mon, Sep 28, 2009 at 4:04 PM, Stef Walter <stef-list@memberwebs.com> wrote:
> Robert Haas wrote:
>> So is this one Ready for Committer?
>
> Here we go, I think this one is ready. In addition to previous patches,
> it does:
>
>  * Use some techniques from postfix for getting interface addresses.
>   Couldn't use code outright, due to license incompatibilities.
>  * Tested on Solaris, FreeBSD, Linux and Windows. As far as I can tell
>   this should also work on Mac OS, HPUX and AIX, and probably others.
>  * Added src/tools/ifaddrs/test_ifaddrs tool for testing interface
>   address code.

Abhijit -

This look ready to you, too?  If so, please mark it as such.

...Robert


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, Sep 28, 2009 at 4:04 PM, Stef Walter <stef-list@memberwebs.com> wrote:
>> �* Tested on Solaris, FreeBSD, Linux and Windows. As far as I can tell
>> � this should also work on Mac OS, HPUX and AIX, and probably others.

> This look ready to you, too?  If so, please mark it as such.

I was just poking at this.  It seems to need rather a lot of
editorialization (eg to fix the lack of consistency about whether
nonstandard headers have configure tests, or bother to make use of the
tests that did get added).  However, it does actually compile and appear
to work on HPUX 10.20, which is my personal benchmark for hopeless
obsolescence ;-).  So modulo the issue about how much we trust the
system-reported netmasks, it seems we could adopt this.
        regards, tom lane


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Stef Walter
Date:
Tom Lane wrote:
> I was just poking at this.

Thanks for trying it out.

It seems to need rather a lot of
> editorialization (eg to fix the lack of consistency about whether
> nonstandard headers have configure tests, or bother to make use of the
> tests that did get added).

I've now added tests for sys/ioctl.h and net/if.h even though these
headers seemed to be common to all the unixes investigated.

The test for ifaddrs.h is to allow the test for getifaddrs() later in
configure.in to work. This is how other open source projects have
handled this situation, but if you'd like me to do it differently for
postgres I can.

> However, it does actually compile and appear
> to work on HPUX 10.20, which is my personal benchmark for hopeless
> obsolescence ;-).

Good news.

So modulo the issue about how much we trust the
> system-reported netmasks, it seems we could adopt this.

FWIW, there are checks for various bad netmasks. I incorporated these
techniques after seeing them in the corresponding postfix code.

BTW, there's also fallback code. If none of the methods work on a given
OS, then the ifaddrs code just lists 127.0.0.1/8 and ::1/128.

Cheers,

Stef
diff --git a/configure.in b/configure.in
index e545a1f..8b42684 100644
*** a/configure.in
--- b/configure.in
*************** AC_SUBST(OSSP_UUID_LIBS)
*** 969,975 ****
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h]) 

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
--- 969,984 ----
  ##

  dnl sys/socket.h is required by AC_FUNC_ACCEPT_ARGTYPES
! AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h langinfo.h poll.h pwd.h sys/ipc.h sys/poll.h sys/pstat.h
sys/resource.hsys/select.h sys/sem.h sys/socket.h sys/shm.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h
wchar.hwctype.h kernel/OS.h kernel/image.h SupportDefs.h ifaddrs.h sys/ioctl.h sys/sockio.h]) 
!
! # On BSD, cpp test for net/if.h will fail unless sys/socket.h
! # is included first, it's checked above.
! AC_CHECK_HEADERS(net/if.h, [], [],
! [AC_INCLUDES_DEFAULT
! #ifdef HAVE_SYS_SOCKET_H
! #include <sys/socket.h>
! #endif
! ])

  # At least on IRIX, cpp test for netinet/tcp.h will fail unless
  # netinet/in.h is included first.
*************** PGAC_VAR_INT_TIMEZONE
*** 1148,1154 ****
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
--- 1157,1163 ----
  AC_FUNC_ACCEPT_ARGTYPES
  PGAC_FUNC_GETTIMEOFDAY_1ARG

! AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle
setsidsigprocmask symlink sysconf towlower utime utimes waitpid wcstombs getifaddrs]) 

  # posix_fadvise() is a no-op on Solaris, so don't incur function overhead
  # by calling it, 2009-04-02
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index ad4d084..e5152f4 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 244,249 ****
--- 244,255 ----
         support for IPv6 addresses.
        </para>

+       <para>Instead of a <replaceable>CIDR-address</replaceable>, you can specify
+        <literal>samehost</literal> to match any of the server's own IP addresses,
+        or <literal>samenet</literal> to match any address in a subnet that the
+        server belongs to.
+       </para>
+
        <para>
         This field only applies to <literal>host</literal>,
         <literal>hostssl</literal>, and <literal>hostnossl</> records.
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index e6f7db2..702971a 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
*************** check_db(const char *dbname, const char
*** 512,517 ****
--- 512,608 ----
      return false;
  }

+ /*
+  * Check to see if a connecting IP matches the address and netmask.
+  */
+ static bool
+ check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+ {
+     if (raddr->addr.ss_family == addr->sa_family)
+     {
+         /* Same address family */
+         if (!pg_range_sockaddr(&raddr->addr, (struct sockaddr_storage*)addr,
+                                (struct sockaddr_storage*)mask))
+             return false;
+     }
+ #ifdef HAVE_IPV6
+     else if (addr->sa_family == AF_INET &&
+              raddr->addr.ss_family == AF_INET6)
+     {
+         /*
+          * 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.
+          */
+         struct sockaddr_storage addrcopy,
+                     maskcopy;
+
+         memcpy(&addrcopy, &addr, sizeof(addrcopy));
+         memcpy(&maskcopy, &mask, sizeof(maskcopy));
+         pg_promote_v4_to_v6_addr(&addrcopy);
+         pg_promote_v4_to_v6_mask(&maskcopy);
+
+         if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy))
+             return false;
+     }
+ #endif   /* HAVE_IPV6 */
+     else
+     {
+         /* Wrong address family, no IPV6 */
+         return false;
+     }
+
+     return true;
+ }
+
+ typedef struct CheckNetwork {
+     NetMethod method;
+     SockAddr *raddr;
+     bool result;
+ } CheckNetwork;
+
+ static void
+ callback_check_network(struct sockaddr *addr, struct sockaddr *netmask, void *data)
+ {
+     CheckNetwork *cn = data;
+     struct sockaddr_storage mask;
+
+     /* Already found a match */
+     if (cn->result)
+         return;
+
+     /* Make a fully 1's netmask of appropriate length */
+     if (cn->method == nmSameHost)
+     {
+         pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
+         cn->result = check_ip(cn->raddr, addr, (struct sockaddr*)&mask);
+     }
+
+     /* Use the netmask of the interface itself */
+     else
+     {
+         cn->result = check_ip(cn->raddr, addr, netmask);
+     }
+ }
+
+ static bool
+ check_same_host_or_net(SockAddr *raddr, NetMethod method)
+ {
+     CheckNetwork cn;
+     cn.method = method;
+     cn.raddr = raddr;
+     cn.result = false;
+
+     if (pg_foreach_ifaddr(callback_check_network, &cn) < 0)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_WARNING),
+                  errmsg("Error enumerating network interfaces")));
+         return false;
+     }
+
+     return cn.result;
+ }

  /*
   * Macros used to check and report on invalid configuration options.
*************** parse_hba_line(List *line, int line_num,
*** 658,756 ****
                                  line_num, HbaFileName)));
              return false;
          }
-         token = pstrdup(lfirst(line_item));

!         /* Check if it has a CIDR suffix and if so isolate it */
!         cidr_slash = strchr(token, '/');
!         if (cidr_slash)
!             *cidr_slash = '\0';
!
!         /* Get the IP address either way */
!         hints.ai_flags = AI_NUMERICHOST;
!         hints.ai_family = PF_UNSPEC;
!         hints.ai_socktype = 0;
!         hints.ai_protocol = 0;
!         hints.ai_addrlen = 0;
!         hints.ai_canonname = NULL;
!         hints.ai_addr = NULL;
!         hints.ai_next = NULL;

!         ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!         if (ret || !gai_result)
          {
!             ereport(LOG,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\": %s",
!                             token, gai_strerror(ret)),
!                      errcontext("line %d of configuration file \"%s\"",
!                                 line_num, HbaFileName)));
!             if (cidr_slash)
!                 *cidr_slash = '/';
!             if (gai_result)
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!             return false;
          }

!         if (cidr_slash)
!             *cidr_slash = '/';
!
!         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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid CIDR mask in address \"%s\"",
!                                 token),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
          }
          else
          {
!             /* Read the mask field. */
!             line_item = lnext(line_item);
!             if (!line_item)
!             {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("end-of-line before netmask specification"),
!                          errcontext("line %d of configuration file \"%s\"",
!                                     line_num, HbaFileName)));
!                 return false;
!             }
!             token = lfirst(line_item);

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
!                 ereport(LOG,
!                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                 HbaFileName, line_num)));
!                 return false;
              }
          }
      }                            /* != ctLocal */
--- 749,868 ----
                                  line_num, HbaFileName)));
              return false;
          }

!         /* Is it equal to 'samehost' or 'samenet'? */
!         token = lfirst(line_item);

!         /* Any IP on this host is allowed to connect */
!         if (strcmp(token, "samehost") == 0)
          {
!             parsedline->net_method = nmSameHost;
          }

!         /* Any IP on the host's subnets is allowed to connect */
!         else if (strcmp(token, "samenet") == 0)
          {
!             parsedline->net_method = nmSameNet;
          }
+
+         /* IP and netmask are specified */
          else
          {
!             parsedline->net_method = nmCompare;
!             token = pstrdup(token);
!
!             /* Check if it has a CIDR suffix and if so isolate it */
!             cidr_slash = strchr(token, '/');
!             if (cidr_slash)
!                 *cidr_slash = '\0';
!
!             /* Get the IP address either way */
!             hints.ai_flags = AI_NUMERICHOST;
!             hints.ai_family = PF_UNSPEC;
!             hints.ai_socktype = 0;
!             hints.ai_protocol = 0;
!             hints.ai_addrlen = 0;
!             hints.ai_canonname = NULL;
!             hints.ai_addr = NULL;
!             hints.ai_next = NULL;

              ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
              if (ret || !gai_result)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP address \"%s\": %s",
                                  token, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 if (cidr_slash)
+                     *cidr_slash = '/';
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return false;
              }

!             if (cidr_slash)
!                 *cidr_slash = '/';
!
!             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 (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                           parsedline->addr.ss_family) < 0)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid CIDR mask in address \"%s\"",
!                                     token),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!             }
!             else
!             {
!                 /* Read the mask field. */
!                 line_item = lnext(line_item);
!                 if (!line_item)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("end-of-line before netmask specification"),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     return false;
!                 }
!                 token = lfirst(line_item);
!
!                 ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
!                 if (ret || !gai_result)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("invalid IP mask \"%s\": %s",
!                                     token, gai_strerror(ret)),
!                              errcontext("line %d of configuration file \"%s\"",
!                                         line_num, HbaFileName)));
!                     if (gai_result)
!                         pg_freeaddrinfo_all(hints.ai_family, gai_result);
!                     return false;
!                 }
!
!                 memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
!                 pg_freeaddrinfo_all(hints.ai_family, gai_result);
!
!                 if (parsedline->addr.ss_family != parsedline->mask.ss_family)
!                 {
!                     ereport(LOG,
!                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                              errmsg("IP address and mask do not match in file \"%s\" line %d",
!                                     HbaFileName, line_num)));
!                     return false;
!                 }
              }
          }
      }                            /* != ctLocal */
*************** check_hba(hbaPort *port)
*** 1096,1131 ****
                  continue;
  #endif

!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
              {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
                      continue;
!             }
! #ifdef HAVE_IPV6
!             else if (hba->addr.ss_family == AF_INET &&
!                      port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * 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.
!                  */
!                 struct sockaddr_storage addrcopy,
!                             maskcopy;
!
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
                      continue;
!             }
! #endif   /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
                  continue;
          }                        /* != ctLocal */

          /* Check database and role */
--- 1208,1228 ----
                  continue;
  #endif

!             switch (hba->net_method)
              {
!             case nmCompare:
!                 if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr,
!                               (struct sockaddr*)&hba->mask))
                      continue;
!                 break;
!             case nmSameHost:
!             case nmSameNet:
!                 if (!check_same_host_or_net(&port->raddr, hba->net_method))
                      continue;
!                 break;
!             default:
                  continue;
+             }
          }                        /* != ctLocal */

          /* Check database and role */
diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c
index 0c35ddd..c9c332b 100644
*** a/src/backend/libpq/ip.c
--- b/src/backend/libpq/ip.c
*************** range_sockaddr_AF_INET6(const struct soc
*** 333,338 ****
--- 333,340 ----
   *    pg_sockaddr_cidr_mask - make a network mask of the appropriate family
   *      and required number of significant bits
   *
+  * numbits can be null, in which case the mask is fully set.
+  *
   * The resulting mask is placed in *mask, which had better be big enough.
   *
   * Return value is 0 if okay, -1 if not.
*************** pg_sockaddr_cidr_mask(struct sockaddr_st
*** 343,352 ****
      long        bits;
      char       *endptr;

!     bits = strtol(numbits, &endptr, 10);
!
!     if (*numbits == '\0' || *endptr != '\0')
!         return -1;

      switch (family)
      {
--- 345,360 ----
      long        bits;
      char       *endptr;

!     if (numbits == NULL)
!     {
!         bits = (family == AF_INET) ? 32 : 128;
!     }
!     else
!     {
!         bits = strtol(numbits, &endptr, 10);
!         if (*numbits == '\0' || *endptr != '\0')
!             return -1;
!     }

      switch (family)
      {
*************** pg_promote_v4_to_v6_mask(struct sockaddr
*** 476,478 ****
--- 484,858 ----
  }

  #endif   /* HAVE_IPV6 */
+
+
+ static void
+ run_ifaddr_callback(PgIfAddrCallback callback, void * cb_data,
+                     struct sockaddr * addr, struct sockaddr * mask)
+ {
+     struct sockaddr_storage full;
+
+     if (!addr)
+         return;
+
+     /* Check that the mask is valid */
+     if (mask)
+     {
+         if (mask->sa_family != addr->sa_family)
+         {
+             mask = NULL;
+         }
+
+         else if (mask->sa_family == AF_INET)
+         {
+             if (((struct sockaddr_in*)mask)->sin_addr.s_addr == INADDR_ANY)
+                 mask = NULL;
+         }
+ #ifdef HAVE_IPV6
+         else if (mask->sa_family == AF_INET6)
+         {
+             if (IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)mask)->sin6_addr))
+                 mask = NULL;
+         }
+ #endif
+     }
+
+     /* If mask is invalid, generate our own fully set mask */
+     if (!mask)
+     {
+         pg_sockaddr_cidr_mask(&full, NULL, addr->sa_family);
+         mask = (struct sockaddr*)&full;
+     }
+
+     (callback)(addr, mask, cb_data);
+ }
+
+ /*
+  * Enumerate network interface addresses on Win32. This uses
+  * the Winsock 2 functions (ie: ws2_32.dll)
+  */
+ #ifdef WIN32
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     INTERFACE_INFO *ptr, *ii = NULL;
+     unsigned long length, i;
+     unsigned long n_ii = 0;
+     SOCKET sock;
+     int error;
+
+     sock = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+     if (sock == SOCKET_ERROR)
+         return -1;
+
+     while (n_ii < 1024)
+     {
+         n_ii += 64;
+         ptr = realloc(ii, sizeof (INTERFACE_INFO) * n_ii);
+         if (!ptr)
+         {
+             free(ii);
+             closesocket(sock);
+             errno = ENOMEM;
+             return -1;
+         }
+
+         ii = ptr;
+         if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, 0, 0,
+                      ii, n_ii * sizeof (INTERFACE_INFO),
+                      &length, 0, 0) == SOCKET_ERROR)
+         {
+             error = WSAGetLastError();
+             if (error == WSAEFAULT || error == WSAENOBUFS)
+                 continue;
+             closesocket(sock);
+             free(ii);
+             return -1;
+         }
+
+         break;
+     }
+
+     for (i = 0; i < length / sizeof(INTERFACE_INFO); ++i)
+         run_ifaddr_callback(callback, cb_data,
+                             (struct sockaddr*)&ii[i].iiAddress,
+                             (struct sockaddr*)&ii[i].iiNetmask);
+
+     closesocket(sock);
+     free(ii);
+     return 0;
+ }
+
+ /*
+  * Enumerate interfaces on BSDs, AIX and modern Linux.
+  * Uses the getifaddrs() interface, clean and simple.
+  */
+
+ #elif HAVE_GETIFADDRS /* && !WIN32 */
+
+ #include <ifaddrs.h>
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifaddrs *ifa, *l;
+
+     if (getifaddrs(&ifa) < 0)
+         return -1;
+
+     for (l = ifa; l; l = l->ifa_next)
+         run_ifaddr_callback(callback, cb_data,
+                             l->ifa_addr, l->ifa_netmask);
+
+     freeifaddrs(ifa);
+     return 0;
+ }
+
+ #else /* !HAVE_GETIFADDRS && !WIN32 */
+
+ #ifdef HAVE_SYS_IOCTL_H
+ #include <sys/ioctl.h>
+ #endif
+
+ #ifdef HAVE_NET_IF_H
+ #include <net/if.h>
+ #endif
+
+ #ifdef HAVE_SYS_SOCKIO_H
+ #include <sys/sockio.h>
+ #endif
+
+ /*
+  * SIOCGIFCONF does not return IPv6 addresses on Solaris,
+  * and HP/UX. So we use SIOCGLIFCONF if it's available.
+  */
+
+ #if defined(SIOCGLIFCONF)
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void *cb_data)
+ {
+     struct lifconf lifc;
+     struct lifreq *lifr, lmask;
+     struct sockaddr *addr, *mask;
+     char *ptr, *buffer = NULL;
+     size_t n_buffer = 1024;
+     int sock, fd;
+ #ifdef HAVE_IPV6
+     int sock6;
+ #endif
+     int i, total;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     while(n_buffer < 1024 * 100)
+     {
+         n_buffer += 1024;
+         ptr = realloc(buffer, n_buffer);
+         if (!ptr)
+         {
+             free(buffer);
+             close(sock);
+             errno = ENOMEM;
+             return -1;
+         }
+
+         memset(&lifc, 0, sizeof (lifc));
+         lifc.lifc_family = AF_UNSPEC;
+         lifc.lifc_len = n_buffer;
+         lifc.lifc_buf = buffer = ptr;
+
+         if (ioctl(sock, SIOCGLIFCONF, &lifc) < 0)
+         {
+             if (errno == EINVAL)
+                 continue;
+             free(buffer);
+             close(sock);
+             return -1;
+         }
+
+         /*
+          * Some Unixes try to return as much data as possible,
+          * no indication of whether enough space allocated.
+          */
+         if (lifc.lifc_len < n_buffer - 1024)
+             break;
+     }
+
+ #ifdef HAVE_IPV6
+     sock6 = socket(AF_INET6, SOCK_DGRAM, 0);
+     if (sock6 == -1)
+     {
+         free(buffer);
+         close(sock);
+         return -1;
+     }
+ #endif
+
+     total = lifc.lifc_len / sizeof(struct lifreq);
+     lifr = lifc.lifc_req;
+     for (i = 0; i < total; ++i)
+     {
+         addr = (struct sockaddr*)&lifr[i].lifr_addr;
+         memcpy(&lmask, &lifr[i], sizeof (struct lifreq));
+ #ifdef HAVE_IPV6
+         fd = addr->sa_family == AF_INET6 ? sock6 : sock;
+ #else
+         fd = sock;
+ #endif
+         if (ioctl(fd, SIOCGLIFNETMASK, &lmask) < 0)
+             mask = NULL;
+         else
+             mask = (struct sockaddr*)&lmask.lifr_addr;
+         run_ifaddr_callback(callback, cb_data, addr, mask);
+     }
+
+     free(buffer);
+     close(sock);
+ #ifdef HAVE_IPV6
+     close(sock6);
+ #endif
+     return 0;
+ }
+
+ /*
+  * Remaining Unixes use SIOCGIFCONF. Some only return IPv4 information
+  * here, so this is the least preferred method. Note that there is no
+  * standard way to iterate the struct ifreq returned in the array.
+  * On some OSs the structures are padded large enough for any address,
+  * on others you have to calculate the size of the struct ifreq.
+  */
+
+ #elif defined(SIOCGIFCONF)
+
+ /* Some OSs have _SIZEOF_ADDR_IFREQ, so just use that */
+ #ifndef _SIZEOF_ADDR_IFREQ
+
+ /* Calculate based on sockaddr.sa_len */
+ #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ #define _SIZEOF_ADDR_IFREQ(ifr) \
+         ((ifr).ifr_addr.sa_len > sizeof(struct sockaddr) ? \
+          (sizeof(struct ifreq) - sizeof(struct sockaddr) + \
+           (ifr).ifr_addr.sa_len) : sizeof(struct ifreq))
+
+ /* Padded ifreq structure, simple */
+ #else
+ #define _SIZEOF_ADDR_IFREQ(ifr) \
+     sizeof (struct ifreq)
+
+ #endif
+ #endif
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct ifconf ifc;
+     struct ifreq *ifr, *end, addr, mask;
+     char *ptr, *buffer = NULL;
+     size_t n_buffer = 1024;
+     int sock;
+
+     sock = socket(AF_INET, SOCK_DGRAM, 0);
+     if (sock == -1)
+         return -1;
+
+     while(n_buffer < 1024 * 100)
+     {
+         n_buffer += 1024;
+         ptr = realloc(buffer, n_buffer);
+         if (!ptr)
+         {
+             free(buffer);
+             close(sock);
+             errno = ENOMEM;
+             return -1;
+         }
+
+         memset(&ifc, 0, sizeof (ifc));
+         ifc.ifc_buf = buffer = ptr;
+         ifc.ifc_len = n_buffer;
+
+         if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
+         {
+             if (errno == EINVAL)
+                 continue;
+             free(buffer);
+             close(sock);
+             return -1;
+         }
+
+         /*
+          * Some Unixes try to return as much data as possible,
+          * no indication of whether enough space allocated.
+          */
+         if (ifc.ifc_len < n_buffer - 1024)
+             break;
+     }
+
+     end = (struct ifreq*)(buffer + ifc.ifc_len);
+     for (ifr = ifc.ifc_req; ifr < end;)
+     {
+         memcpy(&addr, ifr, sizeof(addr));
+         memcpy(&mask, ifr, sizeof(mask));
+         if (ioctl(sock, SIOCGIFADDR, &addr, sizeof(addr)) == 0 &&
+             ioctl(sock, SIOCGIFNETMASK, &mask, sizeof(mask)) == 0)
+             run_ifaddr_callback(callback, cb_data,
+                                 &addr.ifr_addr, &mask.ifr_addr);
+         ifr = (struct ifreq*)((char*)ifr + _SIZEOF_ADDR_IFREQ(*ifr));
+     }
+
+     free(buffer);
+     close(sock);
+     return 0;
+ }
+
+ #else /* !defined(SIOCGIFCONF) */
+
+ /*
+  * No known way to figure out the addresses on the local machine.
+  * So we return the loopbacks for each family.
+  */
+
+ int
+ pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data)
+ {
+     struct sockaddr_in addr;
+     struct sockaddr_storage mask;
+ #ifdef HAVE_IPV6
+     struct sockaddr_in6 addr6;
+ #endif
+
+     /* addr 127.0.0.1 mask 255.0.0.0 */
+     memset(&addr, 0, sizeof(addr));
+     addr.sin_family = AF_INET;
+     addr.sin_addr.s_addr = ntohl(0x7f000001);
+     memset(&mask, 0, sizeof(mask));
+     pg_sockaddr_cidr_mask(&mask, "8", AF_INET);
+     run_ifaddr_callback(callback, cb_data,
+                         (struct sockaddr*)&addr,
+                         (struct sockaddr*)&mask);
+
+ #ifdef HAVE_IPV6
+     /* addr ::1/128 */
+     memset(&addr6, 0, sizeof(addr6));
+     addr6.sin6_family = AF_INET6;
+     addr6.sin6_addr.s6_addr[15] = 1;
+     memset(&mask, 0, sizeof(mask));
+     pg_sockaddr_cidr_mask(&mask, "128", AF_INET6);
+     run_ifaddr_callback(callback, cb_data,
+                         (struct sockaddr*)&addr6,
+                         (struct sockaddr*)&mask);
+ #endif
+
+     return 0;
+ }
+
+ #endif /* !defined(SIOCGIFCONF) */
+
+ #endif /* !HAVE_GETIFADDRS */
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index f1c0457..65966da 100644
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 33,38 ****
--- 33,41 ----
  # (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 specify "samehost" to match any of the
+ # server's own IP addresses, or "samenet" to match any address in a subnet that
+ # the server belongs to.
  #
  # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
  # "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index b538ee4..5193f38 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
*************** typedef enum UserAuth
*** 30,35 ****
--- 30,42 ----
      uaCert
  } UserAuth;

+ typedef enum NetMethod
+ {
+     nmCompare,
+     nmSameHost,
+     nmSameNet
+ } NetMethod;
+
  typedef enum ConnType
  {
      ctLocal,
*************** typedef struct
*** 44,49 ****
--- 51,57 ----
      ConnType    conntype;
      char       *database;
      char       *role;
+     NetMethod    net_method;
      struct sockaddr_storage addr;
      struct sockaddr_storage mask;
      UserAuth    auth_method;
diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h
index 1934957..9bd562c 100644
*** a/src/include/libpq/ip.h
--- b/src/include/libpq/ip.h
*************** extern void pg_promote_v4_to_v6_mask(str
*** 47,50 ****
--- 47,56 ----
  #define IS_AF_UNIX(fam) (0)
  #endif

+ typedef void (*PgIfAddrCallback)(struct sockaddr * addr,
+                                  struct sockaddr * netmask,
+                                  void * cb_data);
+
+ extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void * cb_data);
+
  #endif   /* IP_H */
diff --git a/src/tools/ifaddrs/Makefile b/src/tools/ifaddrs/Makefile
index ...4ec26c1 .
*** a/src/tools/ifaddrs/Makefile
--- b/src/tools/ifaddrs/Makefile
***************
*** 0 ****
--- 1,26 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile for src/tools/ifaddrs
+ #
+ # Copyright (c) 2003-2009, PostgreSQL Global Development Group
+ #
+ # $PostgreSQL$
+ #
+ #-------------------------------------------------------------------------
+
+ subdir = src/tools/ifaddrs
+ top_builddir = ../../..
+ include $(top_builddir)/src/Makefile.global
+
+ libpq_backdir = $(top_builddir)/src/backend/libpq/
+ override CPPFLAGS := -I$(libpq_backdir) $(CPPFLAGS)
+
+ OBJS= test_ifaddrs.o
+
+ all: test_ifaddrs
+
+ test_ifaddrs: test_ifaddrs.o $(libpq_backdir)/ip.o
+     $(CC) $(CFLAGS) test_ifaddrs.o $(libpq_backdir)/ip.o $(LDFLAGS) $(LIBS) -o $@$(X)
+
+ clean distclean maintainer-clean:
+     rm -f test_ifaddrs$(X) $(OBJS)
diff --git a/src/tools/ifaddrs/README b/src/tools/ifaddrs/README
index ...98e84ff .
*** a/src/tools/ifaddrs/README
--- b/src/tools/ifaddrs/README
***************
*** 0 ****
--- 1,10 ----
+ $PostgreSQL$
+
+ ifaddrs
+ =======
+
+ This program looks up all the IPv4 and IPv6 interfaces on the local machine. It is useful for testing that this
functionalityworks on various platforms. 
+
+     Usage:    test_ifaddrs
+
+
diff --git a/src/tools/ifaddrs/test_ifaddrs.c b/src/tools/ifaddrs/test_ifaddrs.c
index ...4e77cbd .
*** a/src/tools/ifaddrs/test_ifaddrs.c
--- b/src/tools/ifaddrs/test_ifaddrs.c
***************
*** 0 ****
--- 1,72 ----
+ /*
+  * $PostgreSQL$
+  *
+  *
+  *    test_ifaddrs.c
+  *        test pg_foreach_ifaddr()
+  */
+
+ #include "postgres.h"
+
+ #include "libpq/ip.h"
+
+ #include <sys/types.h>
+ #include <sys/socket.h>
+
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
+
+ static void
+ print_addr(struct sockaddr *addr)
+ {
+     char buffer[256];
+     int ret, len;
+
+     switch (addr->sa_family)
+     {
+     case AF_INET:
+         len = sizeof (struct sockaddr_in);
+         break;
+     case AF_INET6:
+         len = sizeof (struct sockaddr_in6);
+         break;
+     default:
+         len = sizeof (struct sockaddr_storage);
+         break;
+     }
+
+     ret = getnameinfo(addr, len, buffer, 256, NULL, 0, NI_NUMERICHOST);
+     if (ret != 0)
+         printf ("[unknown:%d]", addr->sa_family);
+     else
+         printf ("%s", buffer);
+ }
+
+ static void
+ callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
+ {
+     printf ("addr: ");
+     print_addr (addr);
+     printf ("  mask: ");
+     print_addr (mask);
+     printf ("\n");
+ }
+
+ int
+ main(int argc, char *argv[])
+ {
+ #ifdef WIN32
+     WSADATA wsaData;
+     if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+         fprintf (stderr, "WSAStartup failed\n");
+     else
+ #endif
+     if (pg_foreach_ifaddr (callback, NULL) < 0)
+         fprintf (stderr, "pg_foreach_ifaddr failed: %s\n", strerror (errno));
+     return 0;
+ }
+
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f5a01b3..2187420 100644
*** a/src/tools/msvc/Mkvcbuild.pm
--- b/src/tools/msvc/Mkvcbuild.pm
*************** sub mkvcbuild
*** 147,152 ****
--- 147,153 ----
      $libpq->AddIncludeDir('src\port');
      $libpq->AddLibrary('wsock32.lib');
      $libpq->AddLibrary('secur32.lib');
+     $libpq->AddLibrary('ws2_32.lib');
      $libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
      $libpq->UseDef('src\interfaces\libpq\libpqdll.def');
      $libpq->ReplaceFile('src\interfaces\libpq\libpqrc.c','src\interfaces\libpq\libpq.rc');

Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Abhijit Menon-Sen
Date:
At 2009-09-30 11:16:57 -0500, stef-list@memberwebs.com wrote:
>
> I've now added tests for sys/ioctl.h and net/if.h even though these
> headers seemed to be common to all the unixes investigated.

Thanks. I've marked this ready for committer now.

> FWIW, there are checks for various bad netmasks. I incorporated these
> techniques after seeing them in the corresponding postfix code. [...]
>
> BTW, there's also fallback code. If none of the methods work on a
> given OS, then the ifaddrs code just lists 127.0.0.1/8 and ::1/128.

Both of these look good.

> +         /*
> +          * 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.
> +          */

I still think this comment should be fixed in _both_ places it now
occurs, but oh well.

(Note: I have not tested under Windows, and I've not tested
test_ifaddrs.c. Also, there are a few lines which introduce
trailing whitespace.)

-- ams


Re: pg_hba.conf: samehost and samenet [REVIEW]

From
Tom Lane
Date:
Stef Walter <stef-list@memberwebs.com> writes:
> [ postgres-hba-samenet-8.patch ]

Applied with some mostly-cosmetic editorialization.
        regards, tom lane


Re: pg_hba.conf: samehost and samenet

From
Stef Walter
Date:
Magnus Hagander wrote:
> On Wed, Aug 19, 2009 at 15:02, Stef Walter<stef-list@memberwebs.com> wrote:
>> Magnus Hagander wrote:
>>> On Wed, Aug 19, 2009 at 03:58, Stef Walter<stef-list@memberwebs.com> wrote:
>>>> Attached is a new patch, which I hope addresses all the concerns raised.
>>> I think you forgot to actually attach the patch....
>> Whoops. Here it is.
> 
> Is there any actual advantage to using getifaddr() on Linux, 

It is in my opinion, it is the most modern and maintainable of the
methods for obtaining network interface address information. Various
unixes have added support for getifaddr() over the years, and (again my
opinion) would probably continue to do so.

> and not
> just use SIOCGIFCONF for all Unixen?

I do know that using SIOCGIFCONF on AIX comes with strange wrinkles and
variable length data structures etc... getifaddrs() on AIX is a far more
maintainable interface.

That said, I'll adjust the patch as you feel is best for the long term
inclusion in the postgresql source.

Cheers,

Stef