From f3aec2c7f51904e7920cd27115fdab831795d96b Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 1 Oct 2009 01:58:58 +0000 Subject: [PATCH] Support "samehost" and "samenet" specifications in pg_hba.conf, by enumerating the machine's IP interfaces to look for a match. Stef Walter --- configure | 77 ++++++- configure.in | 15 +- doc/src/sgml/client-auth.sgml | 9 +- src/backend/libpq/hba.c | 306 ++++++++++++++++++-------- src/backend/libpq/ip.c | 416 ++++++++++++++++++++++++++++++++++- src/backend/libpq/pg_hba.conf.sample | 3 + src/include/libpq/hba.h | 10 +- src/include/libpq/ip.h | 18 +- src/include/pg_config.h.in | 15 ++ src/tools/ifaddrs/Makefile | 27 +++ src/tools/ifaddrs/README | 12 + src/tools/ifaddrs/test_ifaddrs.c | 73 ++++++ src/tools/msvc/Mkvcbuild.pm | 3 +- 13 files changed, 868 insertions(+), 116 deletions(-) create mode 100644 src/tools/ifaddrs/Makefile create mode 100644 src/tools/ifaddrs/README create mode 100644 src/tools/ifaddrs/test_ifaddrs.c diff --git a/configure b/configure index 07cc74c729..3ccb687d5d 100755 --- a/configure +++ b/configure @@ -9691,7 +9691,10 @@ done -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.h sys/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.h wctype.h kernel/OS.h kernel/image.h SupportDefs.h + + + +for ac_header in crypt.h dld.h fp_class.h getopt.h ieeefp.h ifaddrs.h langinfo.h poll.h pwd.h sys/ioctl.h sys/ipc.h sys/poll.h sys/pstat.h sys/resource.h sys/select.h sys/sem.h sys/shm.h sys/socket.h sys/sockio.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h wchar.h wctype.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 @@ -9842,6 +9845,75 @@ fi done +# On BSD, cpp test for net/if.h will fail unless sys/socket.h +# is included first. + +for ac_header in net/if.h +do +as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +{ $as_echo "$as_me:$LINENO: checking for $ac_header" >&5 +$as_echo_n "checking for $ac_header... " >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + $as_echo_n "(cached) " >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + + +#include <$ac_header> +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:$LINENO: $ac_try_echo\"" +$as_echo "$ac_try_echo") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + eval "$as_ac_Header=yes" +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_Header=no" +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +ac_res=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + { $as_echo "$as_me:$LINENO: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +as_val=`eval 'as_val=${'$as_ac_Header'} + $as_echo "$as_val"'` + if test "x$as_val" = x""yes; then + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + # At least on IRIX, cpp test for netinet/tcp.h will fail unless # netinet/in.h is included first. @@ -17327,7 +17399,8 @@ fi -for ac_func in cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle setsid sigprocmask symlink sysconf towlower utime utimes waitpid wcstombs + +for ac_func in cbrt dlopen fcvt fdatasync getifaddrs getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle setsid sigprocmask 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 diff --git a/configure.in b/configure.in index d8cf7ea5d0..0342b60bfd 100644 --- a/configure.in +++ b/configure.in @@ -1,5 +1,5 @@ dnl Process this file with autoconf to produce a configure script. -dnl $PostgreSQL: pgsql/configure.in,v 1.611 2009/09/13 22:18:22 tgl Exp $ +dnl $PostgreSQL: pgsql/configure.in,v 1.612 2009/10/01 01:58:57 tgl Exp $ dnl dnl Developers, please strive to achieve this order: dnl @@ -969,7 +969,16 @@ AC_SUBST(OSSP_UUID_LIBS) ## 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.h sys/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.h wctype.h kernel/OS.h kernel/image.h SupportDefs.h]) +AC_CHECK_HEADERS([crypt.h dld.h fp_class.h getopt.h ieeefp.h ifaddrs.h langinfo.h poll.h pwd.h sys/ioctl.h sys/ipc.h sys/poll.h sys/pstat.h sys/resource.h sys/select.h sys/sem.h sys/shm.h sys/socket.h sys/sockio.h sys/tas.h sys/time.h sys/un.h termios.h ucred.h utime.h wchar.h wctype.h kernel/OS.h kernel/image.h SupportDefs.h]) + +# On BSD, cpp test for net/if.h will fail unless sys/socket.h +# is included first. +AC_CHECK_HEADERS(net/if.h, [], [], +[AC_INCLUDES_DEFAULT +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +]) # At least on IRIX, cpp test for netinet/tcp.h will fail unless # netinet/in.h is included first. @@ -1148,7 +1157,7 @@ PGAC_VAR_INT_TIMEZONE AC_FUNC_ACCEPT_ARGTYPES PGAC_FUNC_GETTIMEOFDAY_1ARG -AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle setsid sigprocmask symlink sysconf towlower utime utimes waitpid wcstombs]) +AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getifaddrs getpeereid getpeerucred getrlimit memmove poll pstat readlink setproctitle setsid sigprocmask 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 diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 8eded32b2b..6442aa1efe 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,4 +1,4 @@ - + Client Authentication @@ -226,6 +226,13 @@ hostnossl database user + Instead of a CIDR-address, you can write + samehost to match any of the server's own IP + addresses, or samenet to match any address in any + subnet that the server is directly connected to. + + + Typical examples of a CIDR-address are 172.20.143.89/32 for a single host, or 172.20.143.0/24 for a small network, or diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 77fe11f05a..43b5e04b46 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.190 2009/09/01 02:54:51 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.191 2009/10/01 01:58:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,6 +42,14 @@ #define MAX_TOKEN 256 +/* callback data for check_network_callback */ +typedef struct check_network_data +{ + IPCompareMethod method; /* test method */ + SockAddr *raddr; /* client's actual address */ + bool result; /* set to true if match */ +} check_network_data; + /* pre-parsed content of HBA config file: list of HbaLine structs */ static List *parsed_hba_lines = NIL; @@ -512,6 +520,99 @@ check_db(const char *dbname, const char *role, Oid roleid, char *param_str) return false; } +/* + * Check to see if a connecting IP matches the given 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) + { + /* + * If we're connected on IPv6 but the file specifies an IPv4 address + * to match against, promote the latter to an IPv6 address + * before trying to match the client's address. + */ + 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; +} + +/* + * pg_foreach_ifaddr callback: does client addr match this machine interface? + */ +static void +check_network_callback(struct sockaddr *addr, struct sockaddr *netmask, + void *cb_data) +{ + check_network_data *cn = (check_network_data *) cb_data; + struct sockaddr_storage mask; + + /* Already found a match? */ + if (cn->result) + return; + + if (cn->method == ipCmpSameHost) + { + /* Make an all-ones netmask of appropriate length for family */ + pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family); + cn->result = check_ip(cn->raddr, addr, (struct sockaddr*) &mask); + } + else + { + /* Use the netmask of the interface itself */ + cn->result = check_ip(cn->raddr, addr, netmask); + } +} + +/* + * Use pg_foreach_ifaddr to check a samehost or samenet match + */ +static bool +check_same_host_or_net(SockAddr *raddr, IPCompareMethod method) +{ + check_network_data cn; + + cn.method = method; + cn.raddr = raddr; + cn.result = false; + + errno = 0; + if (pg_foreach_ifaddr(check_network_callback, &cn) < 0) + { + elog(LOG, "error enumerating network interfaces: %m"); + return false; + } + + return cn.result; +} + /* * Macros used to check and report on invalid configuration options. @@ -658,99 +759,121 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) 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) + token = lfirst(line_item); + + /* Is it equal to 'samehost' or 'samenet'? */ + if (strcmp(token, "samehost") == 0) { - 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; + /* Any IP on this host is allowed to connect */ + parsedline->ip_cmp_method = ipCmpSameHost; } - - 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) + else if (strcmp(token, "samenet") == 0) { - 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; - } + /* Any IP on the host's subnets is allowed to connect */ + parsedline->ip_cmp_method = ipCmpSameNet; } 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); + /* IP and netmask are specified */ + parsedline->ip_cmp_method = ipCmpMask; + + /* need a modifiable copy of token */ + 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 mask \"%s\": %s", + 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; } - memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen); + if (cidr_slash) + *cidr_slash = '/'; + + memcpy(&parsedline->addr, 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) + /* Get the netmask */ + if (cidr_slash) { - 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; + 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 */ @@ -1097,35 +1220,24 @@ check_hba(hbaPort *port) #endif /* Check IP address */ - if (port->raddr.addr.ss_family == hba->addr.ss_family) + switch (hba->ip_cmp_method) { - if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask)) + case ipCmpMask: + if (!check_ip(&port->raddr, + (struct sockaddr *) &hba->addr, + (struct sockaddr *) &hba->mask)) + continue; + break; + case ipCmpSameHost: + case ipCmpSameNet: + if (!check_same_host_or_net(&port->raddr, + hba->ip_cmp_method)) + continue; + break; + default: + /* shouldn't get here, but deem it no-match if so */ 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 */ diff --git a/src/backend/libpq/ip.c b/src/backend/libpq/ip.c index 90ec119d79..55f006f60c 100644 --- a/src/backend/libpq/ip.c +++ b/src/backend/libpq/ip.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/ip.c,v 1.47 2009/06/11 19:00:15 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/ip.c,v 1.48 2009/10/01 01:58:57 tgl Exp $ * * This file and the IPV6 implementation were initially provided by * Nigel Kukard , Linux Based Systems Design @@ -333,6 +333,8 @@ range_sockaddr_AF_INET6(const struct sockaddr_in6 * addr, * 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. @@ -343,10 +345,16 @@ pg_sockaddr_cidr_mask(struct sockaddr_storage * mask, char *numbits, int family) long bits; char *endptr; - bits = strtol(numbits, &endptr, 10); - - if (*numbits == '\0' || *endptr != '\0') - return -1; + if (numbits == NULL) + { + bits = (family == AF_INET) ? 32 : 128; + } + else + { + bits = strtol(numbits, &endptr, 10); + if (*numbits == '\0' || *endptr != '\0') + return -1; + } switch (family) { @@ -476,3 +484,401 @@ pg_promote_v4_to_v6_mask(struct sockaddr_storage * addr) } #endif /* HAVE_IPV6 */ + + +/* + * Run the callback function for the addr/mask, after making sure the + * mask is sane for the addr. + */ +static void +run_ifaddr_callback(PgIfAddrCallback callback, void *cb_data, + struct sockaddr *addr, struct sockaddr *mask) +{ + struct sockaddr_storage fullmask; + + 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(&fullmask, NULL, addr->sa_family); + mask = (struct sockaddr*) &fullmask; + } + + (*callback) (addr, mask, cb_data); +} + +#ifdef WIN32 + +#include +#include + +/* + * Enumerate the system's network interface addresses and call the callback + * for each one. Returns 0 if successful, -1 if trouble. + * + * This version is for Win32. Uses the Winsock 2 functions (ie: ws2_32.dll) + */ +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; /* need to make the buffer bigger */ + 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; +} + +#elif HAVE_GETIFADDRS /* && !WIN32 */ + +#ifdef HAVE_IFADDRS_H +#include +#endif + +/* + * Enumerate the system's network interface addresses and call the callback + * for each one. Returns 0 if successful, -1 if trouble. + * + * This version uses the getifaddrs() interface, which is available on + * BSDs, AIX, and modern Linux. + */ +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 +#endif + +#ifdef HAVE_NET_IF_H +#include +#endif + +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif + +/* + * SIOCGIFCONF does not return IPv6 addresses on Solaris + * and HP/UX. So we prefer SIOCGLIFCONF if it's available. + */ + +#if defined(SIOCGLIFCONF) + +/* + * Enumerate the system's network interface addresses and call the callback + * for each one. Returns 0 if successful, -1 if trouble. + * + * This version uses ioctl(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_buf = buffer = ptr; + lifc.lifc_len = n_buffer; + + 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, + * with no indication of whether enough space allocated. + * Don't believe we have it all unless there's lots of slop. + */ + if (lifc.lifc_len < n_buffer - 1024) + break; + } + +#ifdef HAVE_IPV6 + /* We'll need an IPv6 socket too for the SIOCGLIFNETMASK ioctls */ + 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; +} + +#elif defined(SIOCGIFCONF) + +/* + * 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. + */ + +/* 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 /* !_SIZEOF_ADDR_IFREQ */ + +/* + * Enumerate the system's network interface addresses and call the callback + * for each one. Returns 0 if successful, -1 if trouble. + * + * This version uses ioctl(SIOCGIFCONF). + */ +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, + * with no indication of whether enough space allocated. + * Don't believe we have it all unless there's lots of slop. + */ + 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) */ + +/* + * Enumerate the system's network interface addresses and call the callback + * for each one. Returns 0 if successful, -1 if trouble. + * + * This version is our fallback if there's no known way to get the + * interface addresses. Just return the standard loopback addresses. + */ +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/8 */ + 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 f1c04577d8..cfcd246aae 100644 --- a/src/backend/libpq/pg_hba.conf.sample +++ b/src/backend/libpq/pg_hba.conf.sample @@ -33,6 +33,9 @@ # (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that specifies # the number of significant bits in the mask. Alternatively, you can write # an IP address and netmask in separate columns to specify the set of hosts. +# Instead of a CIDR-address, you can write "samehost" to match any of the +# server's own IP addresses, or "samenet" to match any address in any subnet +# that the server is directly connected to. # # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5", # "ident", "pam", "ldap" or "cert". Note that "password" sends passwords diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 21c9580e68..b444e09ecf 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -4,7 +4,7 @@ * Interface to hba.c * * - * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.58 2009/09/01 03:53:08 tgl Exp $ + * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.59 2009/10/01 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -30,6 +30,13 @@ typedef enum UserAuth uaCert } UserAuth; +typedef enum IPCompareMethod +{ + ipCmpMask, + ipCmpSameHost, + ipCmpSameNet +} IPCompareMethod; + typedef enum ConnType { ctLocal, @@ -46,6 +53,7 @@ typedef struct char *role; struct sockaddr_storage addr; struct sockaddr_storage mask; + IPCompareMethod ip_cmp_method; UserAuth auth_method; char *usermap; diff --git a/src/include/libpq/ip.h b/src/include/libpq/ip.h index 24233dd856..9eb3261c49 100644 --- a/src/include/libpq/ip.h +++ b/src/include/libpq/ip.h @@ -8,7 +8,7 @@ * * Copyright (c) 2003-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/libpq/ip.h,v 1.21 2009/01/01 17:23:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/libpq/ip.h,v 1.22 2009/10/01 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,6 +19,16 @@ #include "libpq/pqcomm.h" +#ifdef HAVE_UNIX_SOCKETS +#define IS_AF_UNIX(fam) ((fam) == AF_UNIX) +#else +#define IS_AF_UNIX(fam) (0) +#endif + +typedef void (*PgIfAddrCallback) (struct sockaddr *addr, + struct sockaddr *netmask, + void *cb_data); + extern int pg_getaddrinfo_all(const char *hostname, const char *servname, const struct addrinfo * hintp, struct addrinfo ** result); @@ -41,10 +51,6 @@ extern void pg_promote_v4_to_v6_addr(struct sockaddr_storage * addr); extern void pg_promote_v4_to_v6_mask(struct sockaddr_storage * addr); #endif -#ifdef HAVE_UNIX_SOCKETS -#define IS_AF_UNIX(fam) ((fam) == AF_UNIX) -#else -#define IS_AF_UNIX(fam) (0) -#endif +extern int pg_foreach_ifaddr(PgIfAddrCallback callback, void *cb_data); #endif /* IP_H */ diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 895e64b144..1af24681fd 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -179,6 +179,9 @@ /* 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 @@ -221,6 +224,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_IEEEFP_H +/* Define to 1 if you have the header file. */ +#undef HAVE_IFADDRS_H + /* Define to 1 if you have the `inet_aton' function. */ #undef HAVE_INET_ATON @@ -336,6 +342,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_NETINET_TCP_H +/* Define to 1 if you have the header file. */ +#undef HAVE_NET_IF_H + /* Define to 1 if you have the `on_exit' function. */ #undef HAVE_ON_EXIT @@ -523,6 +532,9 @@ /* Define to 1 if you have the syslog interface. */ #undef HAVE_SYSLOG +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_IOCTL_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_IPC_H @@ -547,6 +559,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SOCKET_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOCKIO_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H diff --git a/src/tools/ifaddrs/Makefile b/src/tools/ifaddrs/Makefile new file mode 100644 index 0000000000..6f5df48556 --- /dev/null +++ b/src/tools/ifaddrs/Makefile @@ -0,0 +1,27 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/tools/ifaddrs +# +# Copyright (c) 2003-2009, PostgreSQL Global Development Group +# +# $PostgreSQL: pgsql/src/tools/ifaddrs/Makefile,v 1.1 2009/10/01 01:58:58 tgl Exp $ +# +#------------------------------------------------------------------------- + +subdir = src/tools/ifaddrs +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +libpq_backend_dir = $(top_builddir)/src/backend/libpq + +override CPPFLAGS := -I$(libpq_backend_dir) $(CPPFLAGS) + +OBJS = test_ifaddrs.o + +all: test_ifaddrs + +test_ifaddrs: test_ifaddrs.o $(libpq_backend_dir)/ip.o + $(CC) $(CFLAGS) test_ifaddrs.o $(libpq_backend_dir)/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 new file mode 100644 index 0000000000..9b0bd58517 --- /dev/null +++ b/src/tools/ifaddrs/README @@ -0,0 +1,12 @@ +$PostgreSQL: pgsql/src/tools/ifaddrs/README,v 1.1 2009/10/01 01:58:58 tgl Exp $ + +test_ifaddrs +============ + +This program prints the addresses and netmasks of all the IPv4 and IPv6 +interfaces on the local machine. It is useful for testing that this +functionality works on various platforms. If "samehost" and "samenet" +in pg_hba.conf don't seem to work right, run this program to see what +is happening. + +Usage: test_ifaddrs diff --git a/src/tools/ifaddrs/test_ifaddrs.c b/src/tools/ifaddrs/test_ifaddrs.c new file mode 100644 index 0000000000..78013106cc --- /dev/null +++ b/src/tools/ifaddrs/test_ifaddrs.c @@ -0,0 +1,73 @@ +/* + * $PostgreSQL: pgsql/src/tools/ifaddrs/test_ifaddrs.c,v 1.1 2009/10/01 01:58:58 tgl Exp $ + * + * + * test_ifaddrs.c + * test pg_foreach_ifaddr() + */ + +#include "postgres.h" + +#include +#include +#include + +#include "libpq/ip.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; +#ifdef HAVE_IPV6 + case AF_INET6: + len = sizeof(struct sockaddr_in6); + break; +#endif + default: + len = sizeof(struct sockaddr_storage); + break; + } + + ret = getnameinfo(addr, len, buffer, sizeof(buffer), NULL, 0, + NI_NUMERICHOST); + if (ret != 0) + printf("[unknown: family %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"); + return 1; + } +#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 6359f1861e..7579bf5e7d 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -3,7 +3,7 @@ package Mkvcbuild; # # Package that generates build files for msvc build # -# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.42 2009/08/07 20:50:22 petere Exp $ +# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.43 2009/10/01 01:58:58 tgl Exp $ # use Carp; use Win32; @@ -147,6 +147,7 @@ sub mkvcbuild $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'); -- 2.11.0