On Sun, Mar 23, 2014 at 07:04:20PM -0400, Noah Misch wrote: > On Thu, Mar 06, 2014 at 11:52:22PM -0500, Noah Misch wrote: > > On Thu, Mar 06, 2014 at 12:44:34PM -0500, Tom Lane wrote: > > > I'm inclined to suggest that we should put the socket under $CWD by > > > default, but provide some way for the user to override that choice. > > > If they want to put it in /tmp, it's on their head as to how secure > > > that is. On most modern platforms it'd be fine. > > > > I am skeptical about the value of protecting systems with non-sticky /tmp, > > but > > long $CWD isn't of great importance, either. I'm fine with your suggestion. > > Though the $CWD or one of its parents could be world-writable, that would > > typically mean an attacker could just replace the test cases directly. > > Here's the patch. The temporary data directory makes for a convenient socket > directory; initdb already gives it mode 0700, and we have existing > arrangements to purge it when finished. One can override the socket directory > by defining PG_REGRESS_SOCK_DIR in the environment.
Socket path length limitations thwarted that patch: http://www.postgresql.org/message-id/flat/e1wtnv2-00047s...@gemulon.postgresql.org Here's an update that places the socket in a temporary subdirectory of /tmp. The first attached patch adds NetBSD mkdtemp() to libpgport. The second, principal, patch uses mkdtemp() to implement this design in pg_regress. The corresponding change to contrib/pg_upgrade/test.sh is based on the "configure" script's arrangements for its temporary directory. NetBSD's mkdtemp() has assertions, and I initially mapped its assertion macro to our Assert(). However, a bug in our MinGW build process causes build failures if an Assert() call appears in libpgport. I will post about that in a new thread. The affected assertions were uncompelling, so I dropped them. -- Noah Misch EnterpriseDB http://www.enterprisedb.com
diff --git a/configure b/configure index ed1ff0a..f8232db 100755 --- a/configure +++ b/configure @@ -11650,6 +11650,19 @@ esac fi +ac_fn_c_check_func "$LINENO" "mkdtemp" "ac_cv_func_mkdtemp" +if test "x$ac_cv_func_mkdtemp" = xyes; then : + $as_echo "#define HAVE_MKDTEMP 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" mkdtemp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS mkdtemp.$ac_objext" + ;; +esac + +fi + ac_fn_c_check_func "$LINENO" "random" "ac_cv_func_random" if test "x$ac_cv_func_random" = xyes; then : $as_echo "#define HAVE_RANDOM 1" >>confdefs.h diff --git a/configure.in b/configure.in index 80df1d7..c95e2cd 100644 --- a/configure.in +++ b/configure.in @@ -1357,7 +1357,7 @@ else AC_CHECK_FUNCS([fpclass fp_class fp_class_d class], [break]) fi -AC_REPLACE_FUNCS([crypt fls getopt getrusage inet_aton random rint srandom strerror strlcat strlcpy]) +AC_REPLACE_FUNCS([crypt fls getopt getrusage inet_aton mkdtemp random rint srandom strerror strlcat strlcpy]) case $host_os in diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 5ff9e41..4fb7288 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -330,6 +330,9 @@ /* Define to 1 if the system has the type `MINIDUMP_TYPE'. */ #undef HAVE_MINIDUMP_TYPE +/* Define to 1 if you have the `mkdtemp' function. */ +#undef HAVE_MKDTEMP + /* Define to 1 if you have the <netinet/in.h> header file. */ #undef HAVE_NETINET_IN_H diff --git a/src/include/pg_config.h.win32 b/src/include/pg_config.h.win32 index e6e3c8d..58777ca 100644 --- a/src/include/pg_config.h.win32 +++ b/src/include/pg_config.h.win32 @@ -249,6 +249,9 @@ /* Define to 1 if the system has the type `MINIDUMP_TYPE'. */ #define HAVE_MINIDUMP_TYPE 1 +/* Define to 1 if you have the `mkdtemp' function. */ +/* #undef HAVE_MKDTEMP */ + /* Define to 1 if you have the <netinet/in.h> header file. */ #define HAVE_NETINET_IN_H 1 diff --git a/src/include/port.h b/src/include/port.h index c9226f3..3d97481 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -462,6 +462,9 @@ extern int pg_check_dir(const char *dir); /* port/pgmkdirp.c */ extern int pg_mkdir_p(char *path, int omode); +/* port/mkdtemp.c */ +extern char *mkdtemp(char *path); + /* port/pqsignal.c */ typedef void (*pqsigfunc) (int signo); extern pqsigfunc pqsignal(int signo, pqsigfunc func); diff --git a/src/port/mkdtemp.c b/src/port/mkdtemp.c new file mode 100644 index 0000000..a5e991f --- /dev/null +++ b/src/port/mkdtemp.c @@ -0,0 +1,293 @@ +/*------------------------------------------------------------------------- + * + * mkdtemp.c + * create a mode-0700 temporary directory + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/port/mkdtemp.c + * + * This code was taken from NetBSD to provide an implementation for platforms + * that lack it. (Among compatibly-licensed implementations, the OpenBSD + * version better resists denial-of-service attacks. However, it has a + * cryptographic dependency.) The NetBSD copyright terms follow. + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#define _DIAGASSERT(x) do {} while (0) + + +/* $NetBSD: gettemp.c,v 1.17 2014/01/21 19:09:48 seanb Exp $ */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#if !HAVE_NBTOOL_CONFIG_H || !HAVE_MKSTEMP || !HAVE_MKDTEMP + +#ifdef NOT_POSTGRESQL +#include <sys/cdefs.h> +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)mktemp.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: gettemp.c,v 1.17 2014/01/21 19:09:48 seanb Exp $"); +#endif +#endif /* LIBC_SCCS and not lint */ +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef NOT_POSTGRESQL +#if HAVE_NBTOOL_CONFIG_H +#define GETTEMP __nbcompat_gettemp +#else +#include "reentrant.h" +#include "local.h" +#define GETTEMP __gettemp +#endif +#endif + +static int +GETTEMP(char *path, int *doopen, int domkdir) +{ + char *start, + *trv; + struct stat sbuf; + u_int pid; + + /* + * To guarantee multiple calls generate unique names even if the file is + * not created. 676 different possibilities with 7 or more X's, 26 with 6 + * or less. + */ + static char xtra[2] = "aa"; + int xcnt = 0; + + _DIAGASSERT(path != NULL); + /* doopen may be NULL */ + + pid = getpid(); + + /* Move to end of path and count trailing X's. */ + for (trv = path; *trv; ++trv) + if (*trv == 'X') + xcnt++; + else + xcnt = 0; + + /* Use at least one from xtra. Use 2 if more than 6 X's. */ + if (xcnt > 0) + { + *--trv = xtra[0]; + xcnt--; + } + if (xcnt > 5) + { + *--trv = xtra[1]; + xcnt--; + } + + /* Set remaining X's to pid digits with 0's to the left. */ + for (; xcnt > 0; xcnt--) + { + *--trv = (pid % 10) + '0'; + pid /= 10; + } + + /* update xtra for next call. */ + if (xtra[0] != 'z') + xtra[0]++; + else + { + xtra[0] = 'a'; + if (xtra[1] != 'z') + xtra[1]++; + else + xtra[1] = 'a'; + } + + /* + * check the target directory; if you have six X's and it doesn't exist + * this runs for a *very* long time. + */ + for (start = trv + 1;; --trv) + { + if (trv <= path) + break; + if (*trv == '/') + { + int e; + + *trv = '\0'; + e = stat(path, &sbuf); + *trv = '/'; + if (e == -1) + return doopen == NULL && !domkdir; + if (!S_ISDIR(sbuf.st_mode)) + { + errno = ENOTDIR; + return doopen == NULL && !domkdir; + } + break; + } + } + + for (;;) + { + if (doopen) + { + if ((*doopen = + open(path, O_CREAT | O_EXCL | O_RDWR, 0600)) >= 0) + return 1; + if (errno != EEXIST) + return 0; + } + else if (domkdir) + { + if (mkdir(path, 0700) >= 0) + return 1; + if (errno != EEXIST) + return 0; + } + else if (lstat(path, &sbuf)) + return errno == ENOENT ? 1 : 0; + + /* tricky little algorithm for backward compatibility */ + for (trv = start;;) + { + if (!*trv) + return 0; + if (*trv == 'z') + *trv++ = 'a'; + else + { + if (isdigit((unsigned char) *trv)) + *trv = 'a'; + else + ++* trv; + break; + } + } + } + /* NOTREACHED */ +} + +#endif /* !HAVE_NBTOOL_CONFIG_H || !HAVE_MKSTEMP || + * !HAVE_MKDTEMP */ + + +/* $NetBSD: mkdtemp.c,v 1.11 2012/03/15 18:22:30 christos Exp $ */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#if !HAVE_NBTOOL_CONFIG_H || !HAVE_MKDTEMP + +#ifdef NOT_POSTGRESQL + +#include <sys/cdefs.h> +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)mktemp.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: mkdtemp.c,v 1.11 2012/03/15 18:22:30 christos Exp $"); +#endif +#endif /* LIBC_SCCS and not lint */ + +#if HAVE_NBTOOL_CONFIG_H +#define GETTEMP __nbcompat_gettemp +#else +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include "reentrant.h" +#include "local.h" +#define GETTEMP __gettemp +#endif + +#endif + +char * +mkdtemp(char *path) +{ + _DIAGASSERT(path != NULL); + + return GETTEMP(path, NULL, 1) ? path : NULL; +} + +#endif /* !HAVE_NBTOOL_CONFIG_H || !HAVE_MKDTEMP */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 260f630..7fe01e6 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -69,7 +69,7 @@ sub mkvcbuild srandom.c getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c erand48.c snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c pgcheckdir.c pg_crc.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c - qsort.c qsort_arg.c quotes.c system.c + mkdtemp.c qsort.c qsort_arg.c quotes.c system.c sprompt.c tar.c thread.c getopt.c getopt_long.c dirent.c win32env.c win32error.c win32setlocale.c);
diff --git a/contrib/pg_upgrade/test.sh b/contrib/pg_upgrade/test.sh index baa7d47..9d31f9a 100644 --- a/contrib/pg_upgrade/test.sh +++ b/contrib/pg_upgrade/test.sh @@ -17,15 +17,43 @@ set -e unset MAKEFLAGS unset MAKELEVEL -# Set listen_addresses desirably +# Establish how the server will listen for connections testhost=`uname -s` case $testhost in - MINGW*) LISTEN_ADDRESSES="localhost" ;; - *) LISTEN_ADDRESSES="" ;; + MINGW*) + LISTEN_ADDRESSES="localhost" + PGHOST=""; unset PGHOST + ;; + *) + LISTEN_ADDRESSES="" + # Select a socket directory. The algorithm is from the "configure" + # script; the outcome mimics pg_regress.c:make_temp_sockdir(). + PGHOST=$PG_REGRESS_SOCK_DIR + if [ "x$PGHOST" = x ]; then + { + dir=`(umask 077 && + mktemp -d /tmp/pg_upgrade_check-XXXXXX) 2>/dev/null` && + [ -d "$dir" ] + } || + { + dir=/tmp/pg_upgrade_check-$$-$RANDOM + (umask 077 && mkdir "$dir") + } || + { + echo "could not create socket temporary directory in \"/tmp\"" + exit 1 + } + + PGHOST=$dir + trap 'rm -rf "$PGHOST"' 0 + trap 'exit 3' 1 2 13 15 + fi + export PGHOST + ;; esac -POSTMASTER_OPTS="-F -c listen_addresses=$LISTEN_ADDRESSES" +POSTMASTER_OPTS="-F -c listen_addresses=$LISTEN_ADDRESSES -k \"$PGHOST\"" temp_root=$PWD/tmp_check @@ -86,7 +114,6 @@ PGSERVICE=""; unset PGSERVICE PGSSLMODE=""; unset PGSSLMODE PGREQUIRESSL=""; unset PGREQUIRESSL PGCONNECT_TIMEOUT=""; unset PGCONNECT_TIMEOUT -PGHOST=""; unset PGHOST PGHOSTADDR=""; unset PGHOSTADDR # Select a non-conflicting port number, similarly to pg_regress.c diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml index aee049a..13802e8 100644 --- a/doc/src/sgml/regress.sgml +++ b/doc/src/sgml/regress.sgml @@ -58,21 +58,14 @@ make check <warning> <para> - This test method starts a temporary server, which is configured to accept - any connection originating on the local machine. Any local user can gain - database superuser privileges when connecting to this server, and could - in principle exploit all privileges of the operating-system user running - the tests. Therefore, it is not recommended that you use <literal>make - check</> on machines shared with untrusted users. Instead, run the tests - after completing the installation, as described in the next section. - </para> - - <para> - On Unix-like machines, this danger can be avoided if the temporary - server's socket file is made inaccessible to other users, for example - by running the tests in a protected chroot. On Windows, the temporary - server opens a locally-accessible TCP socket, so filesystem protections - cannot help. + On systems lacking Unix-domain sockets, notably Windows, this test method + starts a temporary server configured to accept any connection originating + on the local machine. Any local user can gain database superuser + privileges when connecting to this server, and could in principle exploit + all privileges of the operating-system user running the tests. Therefore, + it is not recommended that you use <literal>make check</> on an affected + system shared with untrusted users. Instead, run the tests after + completing the installation, as described in the next section. </para> </warning> diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 803cf90..27c46ab 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -30,6 +30,7 @@ #endif #include "getopt_long.h" +#include "libpq/pqcomm.h" /* needed for UNIXSOCK_PATH() */ #include "pg_config_paths.h" /* for resultmap we need a list of pairs of strings */ @@ -109,6 +110,12 @@ static const char *progname; static char *logfilename; static FILE *logfile; static char *difffilename; +static const char *sockdir; +#ifdef HAVE_UNIX_SOCKETS +static const char *temp_sockdir; +static char sockself[MAXPGPATH]; +static char socklock[MAXPGPATH]; +#endif static _resultmap *resultmap = NULL; @@ -307,6 +314,82 @@ stop_postmaster(void) } } +#ifdef HAVE_UNIX_SOCKETS +/* + * Remove the socket temporary directory. pg_regress never waits for a + * postmaster exit, so it is indeterminate whether the postmaster has yet to + * unlink the socket and lock file. Unlink them here so we can proceed to + * remove the directory. Ignore errors; leaking a temporary directory is + * unimportant. This can run from a signal handler. The code is not + * acceptable in a Windows signal handler (see initdb.c:trapsig()), but + * Windows is not a HAVE_UNIX_SOCKETS platform. + */ +static void +remove_temp(void) +{ + Assert(temp_sockdir); + unlink(sockself); + unlink(socklock); + rmdir(temp_sockdir); +} + +/* + * Signal handler that calls remove_temp() and reraises the signal. + */ +static void +signal_remove_temp(int signum) +{ + remove_temp(); + + pqsignal(signum, SIG_DFL); + raise(signum); +} + +/* + * Create a temporary directory suitable for the server's Unix-domain socket. + * The directory will have mode 0700 or stricter, so no other OS user can open + * our socket to exploit our use of trust authentication. Most systems + * constrain the length of socket paths well below _POSIX_PATH_MAX, so we + * place the directory under /tmp rather than relative to the possibly-deep + * current working directory. + * + * Compared to using the compiled-in DEFAULT_PGSOCKET_DIR, this also permits + * testing to work in builds that relocate it to a directory not writable to + * the build/test user. + */ +static const char * +make_temp_sockdir(void) +{ + char *template = strdup("/tmp/pg_regress-XXXXXX"); + + temp_sockdir = mkdtemp(template); + if (temp_sockdir == NULL) + { + fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"), + progname, template, strerror(errno)); + exit(2); + } + + /* Stage file names for remove_temp(). Unsafe in a signal handler. */ + UNIXSOCK_PATH(sockself, port, temp_sockdir); + snprintf(socklock, sizeof(socklock), "%s.lock", sockself); + + /* Remove the directory during clean exit. */ + atexit(remove_temp); + + /* + * Remove the directory before dying to the usual signals. Omit SIGQUIT, + * preserving it as a quick, untidy exit. + */ + pqsignal(SIGHUP, signal_remove_temp); + pqsignal(SIGINT, signal_remove_temp); + pqsignal(SIGPIPE, signal_remove_temp); + pqsignal(SIGTERM, signal_remove_temp); + + return temp_sockdir; +} +#endif /* HAVE_UNIX_SOCKETS */ + /* * Check whether string matches pattern * @@ -759,8 +842,7 @@ initialize_environment(void) * the wrong postmaster, or otherwise behave in nondefault ways. (Note * we also use psql's -X switch consistently, so that ~/.psqlrc files * won't mess things up.) Also, set PGPORT to the temp port, and set - * or unset PGHOST depending on whether we are using TCP or Unix - * sockets. + * PGHOST depending on whether we are using TCP or Unix sockets. */ unsetenv("PGDATABASE"); unsetenv("PGUSER"); @@ -769,10 +851,20 @@ initialize_environment(void) unsetenv("PGREQUIRESSL"); unsetenv("PGCONNECT_TIMEOUT"); unsetenv("PGDATA"); +#ifdef HAVE_UNIX_SOCKETS if (hostname != NULL) doputenv("PGHOST", hostname); else - unsetenv("PGHOST"); + { + sockdir = getenv("PG_REGRESS_SOCK_DIR"); + if (!sockdir) + sockdir = make_temp_sockdir(); + doputenv("PGHOST", sockdir); + } +#else + Assert(hostname != NULL); + doputenv("PGHOST", hostname); +#endif unsetenv("PGHOSTADDR"); if (port != -1) { @@ -2067,7 +2159,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc /* * To reduce chances of interference with parallel installations, use * a port number starting in the private range (49152-65535) - * calculated from the version number. + * calculated from the version number. This aids !HAVE_UNIX_SOCKETS + * systems; elsewhere, the use of a private socket directory already + * prevents interference. */ port = 0xC000 | (PG_VERSION_NUM & 0x3FFF); @@ -2240,10 +2334,11 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc */ header(_("starting postmaster")); snprintf(buf, sizeof(buf), - "\"%s/postgres\" -D \"%s/data\" -F%s -c \"listen_addresses=%s\" > \"%s/log/postmaster.log\" 2>&1", - bindir, temp_install, - debug ? " -d 5" : "", - hostname ? hostname : "", + "\"%s/postgres\" -D \"%s/data\" -F%s " + "-c \"listen_addresses=%s\" -k \"%s\" " + "> \"%s/log/postmaster.log\" 2>&1", + bindir, temp_install, debug ? " -d 5" : "", + hostname ? hostname : "", sockdir ? sockdir : "", outputdir); postmaster_pid = spawn_process(buf); if (postmaster_pid == INVALID_PID)
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers