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.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 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.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 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 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 --- 17279,17286 ---- ! ! 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 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.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]) # 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.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 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 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 --- 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 setsid sigprocmask 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
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers