Thread: pg_hba.conf: samehost and samenet
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
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/
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
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
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
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
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
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
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
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/
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 */
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/
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
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
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
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/
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.
[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 */
(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
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/
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;
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/
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
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/
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
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
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
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
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>
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
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
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>
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
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>
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
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
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
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
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');
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
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
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');
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');
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
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
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
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
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');
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
Stef Walter <stef-list@memberwebs.com> writes: > [ postgres-hba-samenet-8.patch ] Applied with some mostly-cosmetic editorialization. regards, tom lane
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