diff --git a/contrib/pg_upgrade/test.sh b/contrib/pg_upgrade/test.sh index 7bbd2c7..3bda790 100644 --- a/contrib/pg_upgrade/test.sh +++ b/contrib/pg_upgrade/test.sh @@ -17,13 +17,20 @@ set -e unset MAKEFLAGS unset MAKELEVEL +# Run a given "initdb" binary and overlay the regression testing +# authentication configuration. +standard_initdb() { + "$1" -N + ../../src/test/regress/pg_regress --config-auth "$PGDATA" +} + # Establish how the server will listen for connections testhost=`uname -s` case $testhost in MINGW*) LISTEN_ADDRESSES="localhost" - PGHOST=""; unset PGHOST + PGHOST=localhost ;; *) LISTEN_ADDRESSES="" @@ -49,11 +56,11 @@ case $testhost in trap 'rm -rf "$PGHOST"' 0 trap 'exit 3' 1 2 13 15 fi - export PGHOST ;; esac POSTMASTER_OPTS="-F -c listen_addresses=$LISTEN_ADDRESSES -k \"$PGHOST\"" +export PGHOST temp_root=$PWD/tmp_check @@ -141,7 +148,7 @@ export EXTRA_REGRESS_OPTS # enable echo so the user can see what is being executed set -x -"$oldbindir"/initdb -N +standard_initdb "$oldbindir"/initdb "$oldbindir"/pg_ctl start -l "$logdir/postmaster1.log" -o "$POSTMASTER_OPTS" -w if "$MAKE" -C "$oldsrc" installcheck; then pg_dumpall -f "$temp_root"/dump1.sql || pg_dumpall1_status=$? @@ -181,7 +188,7 @@ fi PGDATA=$BASE_PGDATA -initdb -N +standard_initdb 'initdb' pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT" diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml index 71196a1..504d8da 100644 --- a/doc/src/sgml/regress.sgml +++ b/doc/src/sgml/regress.sgml @@ -56,19 +56,6 @@ make check failure represents a serious problem. - - - 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 make check on an affected - system shared with untrusted users. Instead, run the tests after - completing the installation, as described in the next section. - - - Because this test method runs a temporary server, it will not work if you did the build as the root user, since the server will not start as diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 63ff50b..2743c8f 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -320,7 +320,7 @@ endef define prove_check $(MKDIR_P) tmp_check/log $(MAKE) -C $(top_builddir) DESTDIR='$(CURDIR)'/tmp_check/install install >'$(CURDIR)'/tmp_check/log/install.log 2>&1 -cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(CURDIR)/tmp_check/install$(bindir):$$PATH" $(call add_to_path,$(ld_library_path_var),$(CURDIR)/tmp_check/install$(libdir)) PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) t/*.pl +cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(CURDIR)/tmp_check/install$(bindir):$$PATH" $(call add_to_path,$(ld_library_path_var),$(CURDIR)/tmp_check/install$(libdir)) top_srcdir='$(top_srcdir)' PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) t/*.pl endef else diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl index 5a95ebd..fb3e7a0 100644 --- a/src/bin/pg_ctl/t/001_start_stop.pl +++ b/src/bin/pg_ctl/t/001_start_stop.pl @@ -1,7 +1,7 @@ use strict; use warnings; use TestLib; -use Test::More tests => 16; +use Test::More tests => 17; my $tempdir = TestLib::tempdir; my $tempdir_short = TestLib::tempdir_short; @@ -14,6 +14,10 @@ command_exit_is([ 'pg_ctl', 'start', '-D', "$tempdir/nonexistent" ], 1, 'pg_ctl start with nonexistent directory'); command_ok([ 'pg_ctl', 'initdb', '-D', "$tempdir/data" ], 'pg_ctl initdb'); +command_ok( + [ "$ENV{top_srcdir}/src/test/regress/pg_regress", '--config-auth', + "$tempdir/data" ], + 'configure authentication'); open CONF, ">>$tempdir/data/postgresql.conf"; print CONF "listen_addresses = ''\n"; print CONF "unix_socket_directories = '$tempdir_short'\n"; diff --git a/src/bin/pg_ctl/t/002_status.pl b/src/bin/pg_ctl/t/002_status.pl index 9502b6f..b8cbbda 100644 --- a/src/bin/pg_ctl/t/002_status.pl +++ b/src/bin/pg_ctl/t/002_status.pl @@ -9,7 +9,7 @@ my $tempdir_short = TestLib::tempdir_short; command_exit_is([ 'pg_ctl', 'status', '-D', "$tempdir/nonexistent" ], 4, 'pg_ctl status with nonexistent directory'); -system_or_bail "initdb -D '$tempdir'/data -A trust >/dev/null"; +standard_initdb "$tempdir/data"; open CONF, ">>$tempdir/data/postgresql.conf"; print CONF "listen_addresses = ''\n"; print CONF "unix_socket_directories = '$tempdir_short'\n"; diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm index 46a8bec..57abdb9 100644 --- a/src/test/perl/TestLib.pm +++ b/src/test/perl/TestLib.pm @@ -7,6 +7,7 @@ use Exporter 'import'; our @EXPORT = qw( tempdir tempdir_short + standard_initdb start_test_server restart_test_server psql @@ -69,6 +70,14 @@ sub tempdir_short return File::Temp::tempdir(CLEANUP => 1); } +sub standard_initdb +{ + my $pgdata = shift; + system_or_bail("initdb -D '$pgdata' -A trust -N >/dev/null"); + system_or_bail("$ENV{top_srcdir}/src/test/regress/pg_regress", + '--config-auth', $pgdata); +} + my ($test_server_datadir, $test_server_logfile); sub start_test_server @@ -78,7 +87,7 @@ sub start_test_server my $tempdir_short = tempdir_short; - system "initdb -D '$tempdir'/pgdata -A trust -N >/dev/null"; + standard_initdb "$tempdir/pgdata"; $ret = system 'pg_ctl', '-D', "$tempdir/pgdata", '-s', '-w', '-l', "$tempdir/logfile", '-o', "--fsync=off -k $tempdir_short --listen-addresses='' --log-statement=all", diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 27c46ab..cb092f9 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -29,6 +29,7 @@ #include #endif +#include "common/username.h" #include "getopt_long.h" #include "libpq/pqcomm.h" /* needed for UNIXSOCK_PATH() */ #include "pg_config_paths.h" @@ -104,6 +105,7 @@ static char *dlpath = PKGLIBDIR; static char *user = NULL; static _stringlist *extraroles = NULL; static _stringlist *extra_install = NULL; +static char *config_auth_datadir = NULL; /* internal variables */ static const char *progname; @@ -965,6 +967,150 @@ initialize_environment(void) load_resultmap(); } +#ifdef ENABLE_SSPI +/* + * Get account and domain/realm names for the current user. This is based on + * pg_SSPI_recvauth(). The returned strings use static storage. + */ +static void +current_windows_user(const char **acct, const char **dom) +{ + static char accountname[MAXPGPATH]; + static char domainname[MAXPGPATH]; + HANDLE token; + TOKEN_USER *tokenuser; + DWORD retlen; + DWORD accountnamesize = sizeof(accountname); + DWORD domainnamesize = sizeof(domainname); + SID_NAME_USE accountnameuse; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token)) + { + fprintf(stderr, + _("%s: could not open process token: error code %lu\n"), + progname, GetLastError()); + exit(2); + } + + if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122) + { + fprintf(stderr, + _("%s: could not get token user size: error code %lu\n"), + progname, GetLastError()); + exit(2); + } + tokenuser = malloc(retlen); + if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen)) + { + fprintf(stderr, + _("%s: could not get token user: error code %lu\n"), + progname, GetLastError()); + exit(2); + } + + if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize, + domainname, &domainnamesize, &accountnameuse)) + { + fprintf(stderr, + _("%s: could not look up account SID: error code %lu\n"), + progname, GetLastError()); + exit(2); + } + + free(tokenuser); + + *acct = accountname; + *dom = domainname; +} + +/* + * Rewrite pg_hba.conf and pg_ident.conf to use SSPI authentication. Permit + * the current OS user to authenticate as the bootstrap superuser and as any + * user named in a --create-role option. + */ +static void +config_sspi_auth(const char *pgdata) +{ + const char *accountname, + *domainname; + const char *username; + char *errstr; + char fname[MAXPGPATH]; + int res; + FILE *hba, + *ident; + _stringlist *sl; + + /* + * "username", the initdb-chosen bootstrap superuser name, may always + * match "accountname", the value SSPI authentication discovers. The + * underlying system functions do not clearly guarantee that. + */ + current_windows_user(&accountname, &domainname); + username = get_user_name(&errstr); + if (username == NULL) + { + fprintf(stderr, "%s: %s\n", progname, errstr); + exit(2); + } + + /* Check a Write outcome and report any error. */ +#define CW(cond) \ + do { \ + if (!(cond)) \ + { \ + fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), \ + progname, fname, strerror(errno)); \ + exit(2); \ + } \ + } while (0) + + res = snprintf(fname, sizeof(fname), "%s/pg_hba.conf", pgdata); + if (res < 0 || res >= sizeof(fname) - 1) + { + /* + * Truncating this name is a fatal error, because we must not fail to + * overwrite an original trust-authentication pg_hba.conf. + */ + fprintf(stderr, _("%s: directory name too long\n"), progname); + exit(2); + } + hba = fopen(fname, "w"); + if (hba == NULL) + { + fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"), + progname, fname, strerror(errno)); + exit(2); + } + CW(fputs("# Configuration written by config_sspi_auth()\n", hba) >= 0); + CW(fputs("host all all 127.0.0.1/32 sspi include_realm=1 map=regress\n", + hba) >= 0); + CW(fclose(hba) == 0); + + snprintf(fname, sizeof(fname), "%s/pg_ident.conf", pgdata); + ident = fopen(fname, "w"); + if (ident == NULL) + { + fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"), + progname, fname, strerror(errno)); + exit(2); + } + CW(fputs("# Configuration written by config_sspi_auth()\n", ident) >= 0); + + /* + * Double-quote for the benefit of account names containing whitespace or + * '#'. Windows forbids the double-quote character itself, so don't + * bother escaping embedded double-quote characters. + */ + CW(fprintf(ident, "regress \"%s@%s\" \"%s\"\n", + accountname, domainname, username) >= 0); + for (sl = extraroles; sl; sl = sl->next) + CW(fprintf(ident, "regress \"%s@%s\" \"%s\"\n", + accountname, domainname, sl->str) >= 0); + CW(fclose(ident) == 0); +} +#endif + /* * Issue a command via psql, connecting to the specified database * @@ -1957,6 +2103,7 @@ help(void) printf(_("Usage:\n %s [OPTION]... [EXTRA-TEST]...\n"), progname); printf(_("\n")); printf(_("Options:\n")); + printf(_(" --config-auth=DATADIR update authentication settings for DATADIR\n")); printf(_(" --create-role=ROLE create the specified role before testing\n")); printf(_(" --dbname=DB use database DB (default \"regression\")\n")); printf(_(" --debug turn on debug mode in programs that are run\n")); @@ -2023,6 +2170,7 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc {"launcher", required_argument, NULL, 21}, {"load-extension", required_argument, NULL, 22}, {"extra-install", required_argument, NULL, 23}, + {"config-auth", required_argument, NULL, 24}, {NULL, 0, NULL, 0} }; @@ -2137,6 +2285,9 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc case 23: add_stringlist_item(&extra_install, optarg); break; + case 24: + config_auth_datadir = pstrdup(optarg); + break; default: /* getopt_long already emitted a complaint */ fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"), @@ -2154,6 +2305,14 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc optind++; } + if (config_auth_datadir) + { +#ifdef ENABLE_SSPI + config_sspi_auth(config_auth_datadir); +#endif + exit(0); + } + if (temp_install && !port_specified_by_user) /* @@ -2298,6 +2457,18 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc fclose(pg_conf); +#ifdef ENABLE_SSPI + + /* + * Since we successfully used the same buffer for the much-longer + * "initdb" command, this can't truncate. + */ + snprintf(buf, sizeof(buf), "%s/data", temp_install); + config_sspi_auth(buf); +#elif !defined(HAVE_UNIX_SOCKETS) +#error Platform has no means to secure the test installation. +#endif + /* * Check if there is a postmaster running already. */ diff --git a/src/tools/msvc/vcregress.pl b/src/tools/msvc/vcregress.pl index b84f70d..1b8fb54 100644 --- a/src/tools/msvc/vcregress.pl +++ b/src/tools/msvc/vcregress.pl @@ -247,6 +247,15 @@ sub contribcheck exit $mstat if $mstat; } +# Run "initdb", then reconfigure authentication. +sub standard_initdb +{ + return ( + system('initdb', '-N') == 0 and system( + "$topdir/$Config/pg_regress/pg_regress", '--config-auth', + $ENV{PGDATA}) == 0); +} + sub upgradecheck { my $status; @@ -258,6 +267,7 @@ sub upgradecheck # i.e. only this version to this version check. That's # what pg_upgrade's "make check" does. + $ENV{PGHOST} = 'localhost'; $ENV{PGPORT} ||= 50432; my $tmp_root = "$topdir/contrib/pg_upgrade/tmp_check"; (mkdir $tmp_root || die $!) unless -d $tmp_root; @@ -275,7 +285,7 @@ sub upgradecheck my $logdir = "$topdir/contrib/pg_upgrade/log"; (mkdir $logdir || die $!) unless -d $logdir; print "\nRunning initdb on old cluster\n\n"; - system("initdb") == 0 or exit 1; + standard_initdb() or exit 1; print "\nStarting old cluster\n\n"; system("pg_ctl start -l $logdir/postmaster1.log -w") == 0 or exit 1; print "\nSetting up data for upgrading\n\n"; @@ -289,7 +299,7 @@ sub upgradecheck system("pg_ctl -m fast stop") == 0 or exit 1; $ENV{PGDATA} = "$data"; print "\nSetting up new cluster\n\n"; - system("initdb") == 0 or exit 1; + standard_initdb() or exit 1; print "\nRunning pg_upgrade\n\n"; system("pg_upgrade -d $data.old -D $data -b $bindir -B $bindir") == 0 or exit 1;