diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out index f237c43..36fdf73 100644 --- a/contrib/dblink/expected/dblink.out +++ b/contrib/dblink/expected/dblink.out @@ -782,18 +782,17 @@ SELECT dblink_disconnect('dtest1'); (1 row) -- test foreign data wrapper functionality -CREATE USER dblink_regression_test; +CREATE ROLE dblink_regression_test; CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw OPTIONS (dbname 'contrib_regression'); CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (server 'localhost'); -- fail, can't specify server here ERROR: invalid option "server" HINT: Valid options in this context are: user, password -CREATE USER MAPPING FOR public SERVER fdtest; +CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (user :'USER'); GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test; GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO dblink_regression_test; -\set ORIGINAL_USER :USER -\c - dblink_regression_test +SET SESSION AUTHORIZATION dblink_regression_test; -- should fail SELECT dblink_connect('myconn', 'fdtest'); ERROR: password is required @@ -821,7 +820,7 @@ SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) 10 | k | {a10,b10,c10} (11 rows) -\c - :ORIGINAL_USER +\c - - REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test; REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_test; DROP USER dblink_regression_test; diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql index 2a10760..30396ed 100644 --- a/contrib/dblink/sql/dblink.sql +++ b/contrib/dblink/sql/dblink.sql @@ -359,25 +359,24 @@ SELECT dblink_error_message('dtest1'); SELECT dblink_disconnect('dtest1'); -- test foreign data wrapper functionality -CREATE USER dblink_regression_test; +CREATE ROLE dblink_regression_test; CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw OPTIONS (dbname 'contrib_regression'); CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (server 'localhost'); -- fail, can't specify server here -CREATE USER MAPPING FOR public SERVER fdtest; +CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (user :'USER'); GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test; GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO dblink_regression_test; -\set ORIGINAL_USER :USER -\c - dblink_regression_test +SET SESSION AUTHORIZATION dblink_regression_test; -- should fail SELECT dblink_connect('myconn', 'fdtest'); -- should succeed SELECT dblink_connect_u('myconn', 'fdtest'); SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]); -\c - :ORIGINAL_USER +\c - - REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test; REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_test; DROP USER dblink_regression_test; diff --git a/contrib/pg_upgrade/test.sh b/contrib/pg_upgrade/test.sh index 1ef80ed..87cf01c 100644 --- a/contrib/pg_upgrade/test.sh +++ b/contrib/pg_upgrade/test.sh @@ -111,10 +111,43 @@ done EXTRA_REGRESS_OPTS="$EXTRA_REGRESS_OPTS --port=$PGPORT" export EXTRA_REGRESS_OPTS +# Use md5 authentication; take the bootstrap superuser password from a +# user-specified file, or generate a password. Prepare password files +# suitable for initdb and libpq. A strong password prevents hostile local +# users from connecting to the test server and using superuser-only features +# to hijack the OS user account. +pwdir=$temp_root/passwords +server_pwfile=${PGTESTPWFILE-$pwdir/superuser_password} +PGPASSFILE=$pwdir/pgpass +export PGPASSFILE +( + # Most of these commands rarely fail, but be paranoid. + set -e + + # Since we can't readily use O_EXCL, create a directory accessible to the + # owner alone and store the files there. + umask 077 + rm -rf "$pwdir" + mkdir "$pwdir" + + if [ -n "${PGTESTPWFILE+set}" ] || pg_genpassword >"$server_pwfile"; then + : + else + cat >&2 <"$PGPASSFILE" +) || exit + # enable echo so the user can see what is being executed set -x -$oldbindir/initdb -N +$oldbindir/initdb -N --auth=md5 --pwfile="$server_pwfile" $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=$? @@ -154,7 +187,7 @@ fi PGDATA=$BASE_PGDATA -initdb -N +initdb -N --auth=md5 --pwfile="$server_pwfile" pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT" diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index ce7a5e3..5a6ad33 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -181,6 +181,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml index 228edf7..72ef536 100644 --- a/doc/src/sgml/ref/initdb.sgml +++ b/doc/src/sgml/ref/initdb.sgml @@ -254,6 +254,8 @@ PostgreSQL documentation Makes initdb read the database superuser's password from a file. The first line of the file is taken as the password. + is available for generating strong + passwords. diff --git a/doc/src/sgml/ref/pg_genpassword.sgml b/doc/src/sgml/ref/pg_genpassword.sgml new file mode 100644 index 0000000..d19b9f1 --- /dev/null +++ b/doc/src/sgml/ref/pg_genpassword.sgml @@ -0,0 +1,82 @@ + + + + + pg_genpassword + 1 + Application + + + + pg_genpassword + generate a strong user password + + + + pg_genpassword + + + + + pg_genpassword + option + + + + + + Description + + + pg_genpassword generates a password from system-provided + entropy and prints it to standard output. The password is not conducive to + human memorization. It suits machine use, particularly automated test + suites. pg_genpassword exits with status 2 if it fails due to + the absence of any supported entropy source. For any other failure cause, + it exits with status 1. + + + + + Options + + + + + + + + + Print the pg_genpassword version and exit. + + + + + + + + + + Show help about pg_genpassword command line + arguments, and exit. + + + + + + + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 87e8e9e..a1dcae2 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -252,6 +252,7 @@ &initdb; + &pgGenpassword; &pgControldata; &pgCtl; &pgResetxlog; diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml index 16b3621..3db8cf8 100644 --- a/doc/src/sgml/regress.sgml +++ b/doc/src/sgml/regress.sgml @@ -56,26 +56,6 @@ make check failure represents a serious problem. - - - 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 make - check on machines shared with untrusted users. Instead, run the tests - after completing the installation, as described in the next section. - - - - 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. - - - 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 @@ -111,6 +91,18 @@ make MAX_CONNECTIONS=10 check runs no more than ten tests concurrently. + + + To protect your operating system user account, the test driver generates a + strong password for the bootstrap superuser account of the temporary + database cluster. If your platform lacks a recognized source of + high-entropy bytes, such as /dev/urandom, make + check will fail at that step. To work around this problem, store a + strong password alone in a text file and set the PGTESTPWFILE + environment variable to the location of that file. This variable can also + be used on systems having a recognized entropy source to avoid draining + the system entropy pool for password generation. + diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c index 46993ba..adcd8f3 100644 --- a/src/backend/utils/adt/encode.c +++ b/src/backend/utils/adt/encode.c @@ -106,96 +106,9 @@ binary_decode(PG_FUNCTION_ARGS) /* - * HEX + * HEX from src/port/pgencode.c */ -static const char hextbl[] = "0123456789abcdef"; - -static const int8 hexlookup[128] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -}; - -unsigned -hex_encode(const char *src, unsigned len, char *dst) -{ - const char *end = src + len; - - while (src < end) - { - *dst++ = hextbl[(*src >> 4) & 0xF]; - *dst++ = hextbl[*src & 0xF]; - src++; - } - return len * 2; -} - -static inline char -get_hex(char c) -{ - int res = -1; - - if (c > 0 && c < 127) - res = hexlookup[(unsigned char) c]; - - if (res < 0) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid hexadecimal digit: \"%c\"", c))); - - return (char) res; -} - -unsigned -hex_decode(const char *src, unsigned len, char *dst) -{ - const char *s, - *srcend; - char v1, - v2, - *p; - - srcend = src + len; - s = src; - p = dst; - while (s < srcend) - { - if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r') - { - s++; - continue; - } - v1 = get_hex(*s++) << 4; - if (s >= srcend) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid hexadecimal data: odd number of digits"))); - - v2 = get_hex(*s++); - *p++ = v1 | v2; - } - - return p - dst; -} - -static unsigned -hex_enc_len(const char *src, unsigned srclen) -{ - return srclen << 1; -} - -static unsigned -hex_dec_len(const char *src, unsigned srclen) -{ - return srclen >> 1; -} - /* * BASE64 */ diff --git a/src/bin/Makefile b/src/bin/Makefile index f03cc42..cf4358d 100644 --- a/src/bin/Makefile +++ b/src/bin/Makefile @@ -13,8 +13,8 @@ subdir = src/bin top_builddir = ../.. include $(top_builddir)/src/Makefile.global -SUBDIRS = initdb pg_ctl pg_dump \ - psql scripts pg_config pg_controldata pg_resetxlog pg_basebackup +SUBDIRS = initdb pg_ctl pg_dump psql scripts pg_config pg_controldata \ + pg_resetxlog pg_basebackup pg_genpassword ifeq ($(PORTNAME), win32) SUBDIRS += pgevent diff --git a/src/bin/pg_genpassword/.gitignore b/src/bin/pg_genpassword/.gitignore new file mode 100644 index 0000000..a310f5b --- /dev/null +++ b/src/bin/pg_genpassword/.gitignore @@ -0,0 +1 @@ +/pg_genpassword diff --git a/src/bin/pg_genpassword/Makefile b/src/bin/pg_genpassword/Makefile new file mode 100644 index 0000000..fd6c57d --- /dev/null +++ b/src/bin/pg_genpassword/Makefile @@ -0,0 +1,36 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/bin/pg_genpassword +# +# Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/bin/pg_genpassword/Makefile +# +#------------------------------------------------------------------------- + +PGFILEDESC = "pg_genpassword - generate a strong user password" +PGAPPICON=win32 + +subdir = src/bin/pg_genpassword +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +OBJS= pg_genpassword.o $(WIN32RES) + +all: pg_genpassword + +pg_genpassword: $(OBJS) | submake-libpgport + $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +install: all installdirs + $(INSTALL_PROGRAM) pg_genpassword$(X) '$(DESTDIR)$(bindir)/pg_genpassword$(X)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(bindir)' + +uninstall: + rm -f '$(DESTDIR)$(bindir)/pg_genpassword$(X)' + +clean distclean maintainer-clean: + rm -f pg_genpassword$(X) $(OBJS) diff --git a/src/bin/pg_genpassword/nls.mk b/src/bin/pg_genpassword/nls.mk new file mode 100644 index 0000000..64981eb --- /dev/null +++ b/src/bin/pg_genpassword/nls.mk @@ -0,0 +1,4 @@ +# src/bin/pg_genpassword/nls.mk +CATALOG_NAME = pg_genpassword +AVAIL_LANGUAGES = +GETTEXT_FILES = pg_genpassword.c ../../port/exec.c diff --git a/src/bin/pg_genpassword/pg_genpassword.c b/src/bin/pg_genpassword/pg_genpassword.c new file mode 100644 index 0000000..a1ee5f9 --- /dev/null +++ b/src/bin/pg_genpassword/pg_genpassword.c @@ -0,0 +1,87 @@ +/*------------------------------------------------------------------------- + * + * pg_genpassword.c - generate a strong user password + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/bin/pg_genpassword/pg_genpassword.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +const char *progname; + +static void +usage(void) +{ + printf(_("%s generates a strong user password.\n\n"), + progname); + printf(_("Usage:\n")); + printf(_(" %s [OPTION]...\n"), progname); + printf(_("\nOptions:\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_("\nReport bugs to .\n")); +} + +int +main(int argc, char **argv) +{ + char entropy[16]; + unsigned hex_len; + char *password; + + progname = get_progname(argv[0]); + set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_genpassword")); + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + usage(); + exit(0); + } + else if (strcmp(argv[1], "-V") == 0 + || strcmp(argv[1], "--version") == 0) + { + puts("pg_genpassword (PostgreSQL) " PG_VERSION); + exit(0); + } + else + { + fprintf(stderr, + _("%s: too many command-line arguments (first is \"%s\")\n"), + progname, argv[1]); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + } + + + /* + * Generate a sequence of 16 secure random bytes, hex-encode them, and + * print the result. + */ + if (!secure_rand_bytes((unsigned char *) entropy, sizeof(entropy))) + { + fprintf(stderr, _("%s: could not gather system entropy\n"), progname); + exit(2); + } + + hex_len = hex_enc_len(entropy, sizeof(entropy)); + password = malloc(hex_len + 1); + if (password == NULL) + { + fprintf(stderr, _("%s: out of memory\n"), progname); + exit(1); + } + hex_encode(entropy, sizeof(entropy), password); + password[hex_len] = '\0'; + + puts(password); + + return 0; +} diff --git a/src/include/port.h b/src/include/port.h index aeb7754..f5662c1 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -373,6 +373,8 @@ extern double pg_erand48(unsigned short xseed[3]); extern long pg_lrand48(void); extern void pg_srand48(long seed); +extern bool secure_rand_bytes(unsigned char *buf, int count); + #ifndef HAVE_FLS extern int fls(int mask); #endif @@ -462,6 +464,12 @@ extern int pg_check_dir(const char *dir); /* port/pgmkdirp.c */ extern int pg_mkdir_p(char *path, int omode); +/* port/pgencode.c */ +extern unsigned hex_encode(const char *src, unsigned len, char *dst); +extern unsigned hex_decode(const char *src, unsigned len, char *dst); +extern unsigned hex_enc_len(const char *src, unsigned srclen); +extern unsigned hex_dec_len(const char *src, unsigned srclen); + /* port/pqsignal.c */ typedef void (*pqsigfunc) (int signo); extern pqsigfunc pqsignal(int signo, pqsigfunc func); diff --git a/src/interfaces/ecpg/test/connect/test5.pgc b/src/interfaces/ecpg/test/connect/test5.pgc index d3efecb..5ba59eb 100644 --- a/src/interfaces/ecpg/test/connect/test5.pgc +++ b/src/interfaces/ecpg/test/connect/test5.pgc @@ -21,7 +21,9 @@ exec sql end declare section; ECPGdebug(1, stderr); exec sql connect to connectdb as main; + exec sql alter user connectdb ENCRYPTED PASSWORD 'insecure'; exec sql alter user connectuser ENCRYPTED PASSWORD 'connectpw'; + exec sql commit; exec sql disconnect; /* <-- "main" not specified */ strcpy(db, "connectdb"); @@ -38,28 +40,28 @@ exec sql end declare section; exec sql connect to 'connectdb' as main; exec sql disconnect main; - exec sql connect to as main user connectdb; + exec sql connect to as main user connectdb/insecure; exec sql disconnect main; - exec sql connect to connectdb as main user connectuser/connectdb; + exec sql connect to connectdb as main user connectuser/connectpw; exec sql disconnect main; - exec sql connect to unix:postgresql://localhost/connectdb as main user connectuser; + exec sql connect to unix:postgresql://localhost/connectdb as main user connectuser/connectpw; exec sql disconnect main; - exec sql connect to "unix:postgresql://localhost/connectdb" as main user connectuser; + exec sql connect to "unix:postgresql://localhost/connectdb" as main user connectuser/connectpw; exec sql disconnect main; - exec sql connect to 'unix:postgresql://localhost/connectdb' as main user :user; + exec sql connect to 'unix:postgresql://localhost/connectdb' as main user :user USING "connectpw"; exec sql disconnect main; - exec sql connect to unix:postgresql://localhost/connectdb?connect_timeout=14&client_encoding=latin1 as main user connectuser; + exec sql connect to unix:postgresql://localhost/connectdb?connect_timeout=14&client_encoding=latin1 as main user connectuser/connectpw; exec sql disconnect main; - exec sql connect to "unix:postgresql://200.46.204.71/connectdb" as main user connectuser; + exec sql connect to "unix:postgresql://200.46.204.71/connectdb" as main user connectuser/connectpw; exec sql disconnect main; - exec sql connect to unix:postgresql://localhost/ as main user connectdb; + exec sql connect to unix:postgresql://localhost/ as main user connectdb IDENTIFIED BY insecure; exec sql disconnect main; /* connect twice */ diff --git a/src/interfaces/ecpg/test/expected/connect-test5.c b/src/interfaces/ecpg/test/expected/connect-test5.c index a8f79f9..79decd3 100644 --- a/src/interfaces/ecpg/test/expected/connect-test5.c +++ b/src/interfaces/ecpg/test/expected/connect-test5.c @@ -43,113 +43,119 @@ main(void) { ECPGconnect(__LINE__, 0, "connectdb" , NULL, NULL , "main", 0); } #line 23 "test5.pgc" - { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "alter user connectuser encrypted password 'connectpw'", ECPGt_EOIT, ECPGt_EORT);} + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "alter user connectdb encrypted password 'insecure'", ECPGt_EOIT, ECPGt_EORT);} #line 24 "test5.pgc" - { ECPGdisconnect(__LINE__, "CURRENT");} + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "alter user connectuser encrypted password 'connectpw'", ECPGt_EOIT, ECPGt_EORT);} #line 25 "test5.pgc" + + { ECPGtrans(__LINE__, NULL, "commit");} +#line 26 "test5.pgc" + + { ECPGdisconnect(__LINE__, "CURRENT");} +#line 27 "test5.pgc" /* <-- "main" not specified */ strcpy(db, "connectdb"); strcpy(id, "main"); { ECPGconnect(__LINE__, 0, db , NULL, NULL , id, 0); } -#line 29 "test5.pgc" +#line 31 "test5.pgc" { ECPGdisconnect(__LINE__, id);} -#line 30 "test5.pgc" +#line 32 "test5.pgc" { ECPGconnect(__LINE__, 0, "connectdb" , NULL, NULL , "main", 0); } -#line 32 "test5.pgc" +#line 34 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 33 "test5.pgc" +#line 35 "test5.pgc" { ECPGconnect(__LINE__, 0, "connectdb" , NULL, NULL , "main", 0); } -#line 35 "test5.pgc" +#line 37 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 36 "test5.pgc" +#line 38 "test5.pgc" { ECPGconnect(__LINE__, 0, "connectdb" , NULL, NULL , "main", 0); } -#line 38 "test5.pgc" +#line 40 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 39 "test5.pgc" +#line 41 "test5.pgc" - { ECPGconnect(__LINE__, 0, "" , "connectdb" , NULL , "main", 0); } -#line 41 "test5.pgc" + { ECPGconnect(__LINE__, 0, "" , "connectdb" , "insecure" , "main", 0); } +#line 43 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 42 "test5.pgc" +#line 44 "test5.pgc" - { ECPGconnect(__LINE__, 0, "connectdb" , "connectuser" , "connectdb" , "main", 0); } -#line 44 "test5.pgc" + { ECPGconnect(__LINE__, 0, "connectdb" , "connectuser" , "connectpw" , "main", 0); } +#line 46 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 45 "test5.pgc" +#line 47 "test5.pgc" - { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb" , "connectuser" , NULL , "main", 0); } -#line 47 "test5.pgc" + { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb" , "connectuser" , "connectpw" , "main", 0); } +#line 49 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 48 "test5.pgc" +#line 50 "test5.pgc" - { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb" , "connectuser" , NULL , "main", 0); } -#line 50 "test5.pgc" + { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb" , "connectuser" , "connectpw" , "main", 0); } +#line 52 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 51 "test5.pgc" +#line 53 "test5.pgc" - { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb" , user , NULL , "main", 0); } -#line 53 "test5.pgc" + { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb" , user , "connectpw" , "main", 0); } +#line 55 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 54 "test5.pgc" +#line 56 "test5.pgc" - { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb?connect_timeout=14 & client_encoding=latin1" , "connectuser" , NULL , "main", 0); } -#line 56 "test5.pgc" + { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/connectdb?connect_timeout=14 & client_encoding=latin1" , "connectuser" , "connectpw" , "main", 0); } +#line 58 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 57 "test5.pgc" +#line 59 "test5.pgc" - { ECPGconnect(__LINE__, 0, "unix:postgresql://200.46.204.71/connectdb" , "connectuser" , NULL , "main", 0); } -#line 59 "test5.pgc" + { ECPGconnect(__LINE__, 0, "unix:postgresql://200.46.204.71/connectdb" , "connectuser" , "connectpw" , "main", 0); } +#line 61 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 60 "test5.pgc" +#line 62 "test5.pgc" - { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/" , "connectdb" , NULL , "main", 0); } -#line 62 "test5.pgc" + { ECPGconnect(__LINE__, 0, "unix:postgresql://localhost/" , "connectdb" , "insecure" , "main", 0); } +#line 64 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 63 "test5.pgc" +#line 65 "test5.pgc" /* connect twice */ { ECPGconnect(__LINE__, 0, "connectdb" , NULL, NULL , "main", 0); } -#line 66 "test5.pgc" +#line 68 "test5.pgc" { ECPGconnect(__LINE__, 0, "connectdb" , NULL, NULL , "main", 0); } -#line 67 "test5.pgc" +#line 69 "test5.pgc" { ECPGdisconnect(__LINE__, "main");} -#line 68 "test5.pgc" +#line 70 "test5.pgc" /* not connected */ { ECPGdisconnect(__LINE__, "nonexistant");} -#line 71 "test5.pgc" +#line 73 "test5.pgc" return (0); diff --git a/src/interfaces/ecpg/test/expected/connect-test5.stderr b/src/interfaces/ecpg/test/expected/connect-test5.stderr index 9c8dbf2..c856960 100644 --- a/src/interfaces/ecpg/test/expected/connect-test5.stderr +++ b/src/interfaces/ecpg/test/expected/connect-test5.stderr @@ -2,12 +2,20 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ECPGconnect: opening database connectdb on port [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 24: query: alter user connectuser encrypted password 'connectpw'; with 0 parameter(s) on connection main +[NO_PID]: ecpg_execute on line 24: query: alter user connectdb encrypted password 'insecure'; with 0 parameter(s) on connection main [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_execute on line 24: using PQexec [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_process_output on line 24: OK: ALTER ROLE [NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 25: query: alter user connectuser encrypted password 'connectpw'; with 0 parameter(s) on connection main +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 25: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 25: OK: ALTER ROLE +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ECPGtrans on line 26: action "commit"; connection "main" +[NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_finish: connection main closed [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ECPGconnect: opening database connectdb on port @@ -50,11 +58,11 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_finish: connection main closed [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ECPGconnect: non-localhost access via sockets on line 59 +[NO_PID]: ECPGconnect: non-localhost access via sockets on line 61 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: raising sqlcode -402 on line 59: could not connect to database "connectdb" on line 59 +[NO_PID]: raising sqlcode -402 on line 61: could not connect to database "connectdb" on line 61 [NO_PID]: sqlca: code: -402, state: 08001 -[NO_PID]: raising sqlcode -220 on line 60: connection "main" does not exist on line 60 +[NO_PID]: raising sqlcode -220 on line 62: connection "main" does not exist on line 62 [NO_PID]: sqlca: code: -220, state: 08003 [NO_PID]: ECPGconnect: opening database on port for user connectdb [NO_PID]: sqlca: code: 0, state: 00000 @@ -66,5 +74,5 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_finish: connection main closed [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: raising sqlcode -220 on line 71: connection "nonexistant" does not exist on line 71 +[NO_PID]: raising sqlcode -220 on line 73: connection "nonexistant" does not exist on line 73 [NO_PID]: sqlca: code: -220, state: 08003 diff --git a/src/port/Makefile b/src/port/Makefile index 1be4ff5..2a356ec 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -31,8 +31,8 @@ override CPPFLAGS := -I$(top_builddir)/src/port -DFRONTEND $(CPPFLAGS) LIBS += $(PTHREAD_LIBS) OBJS = $(LIBOBJS) chklocale.o dirmod.o erand48.o fls.o inet_net_ntop.o \ - noblock.o path.o pgcheckdir.o pg_crc.o pgmkdirp.o pgsleep.o \ - pgstrcasecmp.o pqsignal.o \ + noblock.o path.o pgcheckdir.o pg_crc.o pgencode.o pgmkdirp.o \ + pgrand.o pgsleep.o pgstrcasecmp.o pqsignal.o \ qsort.o qsort_arg.o quotes.o sprompt.o tar.o thread.o # foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND diff --git a/src/port/pgencode.c b/src/port/pgencode.c new file mode 100644 index 0000000..313af7c --- /dev/null +++ b/src/port/pgencode.c @@ -0,0 +1,120 @@ +/*------------------------------------------------------------------------- + * + * pgencode.c + * Data encode/decode algorithms used in both frontend and backend code + * + * Copyright (c) 2001-2014, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/port/pgencode.c + * + *------------------------------------------------------------------------- + */ + +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +/* + * On success, decode functions return the number of bytes written. In the + * backend, they raise errors using ereport(ERROR). In frontend programs, + * they instead return -1. Other functions defined in this file never fail. + */ +#ifdef FRONTEND +#define ereport(elevel, rest) return -1 +#endif + + +/* + * HEX + */ + +static const char hextbl[] = "0123456789abcdef"; + +static const int8 hexlookup[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +unsigned +hex_encode(const char *src, unsigned len, char *dst) +{ + const char *end = src + len; + + while (src < end) + { + *dst++ = hextbl[(*src >> 4) & 0xF]; + *dst++ = hextbl[*src & 0xF]; + src++; + } + return len * 2; +} + +static inline char +get_hex(char c) +{ + int res = -1; + + if (c > 0 && c < 127) + res = hexlookup[(unsigned char) c]; + + if (res < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid hexadecimal digit: \"%c\"", c))); + + return (char) res; +} + +unsigned +hex_decode(const char *src, unsigned len, char *dst) +{ + const char *s, + *srcend; + char v1, + v2, + *p; + + srcend = src + len; + s = src; + p = dst; + while (s < srcend) + { + if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r') + { + s++; + continue; + } + v1 = get_hex(*s++) << 4; + if (s >= srcend) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid hexadecimal data: odd number of digits"))); + + v2 = get_hex(*s++); + *p++ = v1 | v2; + } + + return p - dst; +} + +unsigned +hex_enc_len(const char *src, unsigned srclen) +{ + return srclen << 1; +} + +unsigned +hex_dec_len(const char *src, unsigned srclen) +{ + return srclen >> 1; +} diff --git a/src/port/pgrand.c b/src/port/pgrand.c new file mode 100644 index 0000000..604f6d2 --- /dev/null +++ b/src/port/pgrand.c @@ -0,0 +1,119 @@ +/* + * pgrand.c + * Cryptographically secure pseudo-random numbers + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Copyright (c) 2001 Marko Kreen + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * src/port/pgrand.c + */ + +#include "c.h" + +#include +#include + +#ifdef USE_SSL +#include +#endif + +#if !defined(USE_SSL) && !defined(WIN32) +/* Wrapper for read(2) that never returns short. */ +static int +safe_read(int fd, void *buf, size_t count) +{ + int done = 0; + char *p = buf; + int res; + + while (count) + { + res = read(fd, p, count); + if (res <= 0) + { + if (errno == EINTR) + continue; + return res; + } + p += res; + done += res; + count -= res; + } + return done; +} +#endif + +/* + * secure_rand_bytes: retrieve an arbitrary quantity of random bytes + * + * Current callers need no more than 16 bytes per process, so seeding a PRNG + * costs as much as simply using the OS entropy directly. When available, use + * the OpenSSL PRNG; if for no other reason, it supports a wider range of + * entropy sources. In the absence of OpenSSL, retrieve bytes directly from + * an OS source. If we ever add a high-volume caller, consider importing + * pgcrypto's PRNG for use in non-SSL builds. + * + * We never fall back to emitting low-entropy sequences. Callers should do so + * where acceptable. + */ +bool +secure_rand_bytes(unsigned char *buf, int count) +{ +#if defined(USE_SSL) + return RAND_bytes(buf, count) == 1; +#elif defined(WIN32) + int res; + HCRYPTPROV h = 0; + + res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL, + (CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET)); + if (!res) + res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET | CRYPT_NEWKEYSET); + if (!res) + return false; + + res = CryptGenRandom(h, count, buf); + if (!res) + return false; + + CryptReleaseContext(h, 0); + return true; +#else + int fd; + int res; + + fd = open("/dev/urandom", O_RDONLY, 0); + if (fd == -1) + { + fd = open("/dev/random", O_RDONLY, 0); + if (fd == -1) + return false; + } + res = safe_read(fd, buf, count); + close(fd); + return res == count; +#endif +} diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 3a49244..4724b43 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -19,6 +19,7 @@ #include "pg_regress.h" #include +#include #include #include #include @@ -92,6 +93,7 @@ static char *encoding = NULL; static _stringlist *schedulelist = NULL; static _stringlist *extra_tests = NULL; static char *temp_install = NULL; +static char *server_pwfile = NULL; static char *temp_config = NULL; static char *top_builddir = NULL; static bool nolocale = false; @@ -682,6 +684,114 @@ add_to_path(const char *pathname, char separator, const char *addval) } /* + * Take the bootstrap superuser password from a user-specified file, or + * generate a password. Prepare password files suitable for initdb and libpq. + * A strong password prevents hostile local users from connecting to the test + * server and using superuser-only features to hijack the OS user account. + * + * The ability to specify a password is mostly for the benefit of systems + * lacking the infrastructure for us to generate one. Folks could do so for + * other reasons, such as to relieve pressure on system entropy. + */ +static void +initialize_password(void) +{ + char file_buf[MAXPGPATH * 2], + password_buf[MAXPGPATH], + pgpass_buf[MAXPGPATH + 32]; + int fd; + int len_pgpass; + + server_pwfile = getenv("PGTESTPWFILE"); + if (server_pwfile != NULL) + { + int len; + + /* Read the password so we can create a PGPASSFILE. */ + fd = open(server_pwfile, O_RDONLY); + if (fd < 0 + || (len = read(fd, password_buf, sizeof(password_buf))) < 1 + || close(fd) != 0) + { + fprintf(stderr, _("%s: could not read file \"%s\": %s\n"), + progname, server_pwfile, strerror(errno)); + exit(2); + } + + if (len >= sizeof(password_buf)) + { + fprintf(stderr, + _("%s: password file \"%s\" must be shorter than %u bytes\n"), + progname, server_pwfile, (unsigned) sizeof(password_buf)); + exit(2); + } + + password_buf[len] = '\0'; + } + else + { + FILE *child; + int len; + + /* Generate a password. */ + snprintf(file_buf, sizeof(file_buf), + SYSTEMQUOTE "\"%s/pg_genpassword\"" SYSTEMQUOTE, bindir); + child = popen(file_buf, "r"); + + if (child == NULL + || fgets(password_buf, sizeof(password_buf), child) == NULL + || (len = strlen(password_buf)) < 32 + || pclose(child) != 0) + { + fputs(_("Failed to generate a strong password for the test superuser. Set the\n" + "PGTESTPWFILE environment variable to the absolute path of a file containing a\n" + "strong password, which need not and should not match one you use elsewhere.\n"), + stderr); + exit(2); + } + + /* Write the file for "initdb --pwfile=..." */ + snprintf(file_buf, sizeof(file_buf), + "%s/superuser_password", temp_install); + server_pwfile = strdup(file_buf); + + /* XXX should we worry about configurations that ignore O_EXCL? */ + fd = open(server_pwfile, O_WRONLY | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR); + if (fd < 0 + || write(fd, password_buf, len) != len + || close(fd) != 0) + { + fprintf(stderr, _("%s: could not write file \"%s\": %s\n"), + progname, server_pwfile, strerror(errno)); + exit(2); + } + } + + /* + * Write the PGPASSFILE. We don't escape backslashes or colons, so + * user-supplied passwords bearing those characters will not work. + * Hackers can cope with that. + */ + snprintf(file_buf, sizeof(file_buf), "%s/pgpass", temp_install); + doputenv("PGPASSFILE", file_buf); + + len_pgpass = snprintf(pgpass_buf, sizeof(pgpass_buf), + "*:*:*:*:%s", password_buf); + Assert(len_pgpass < sizeof(pgpass_buf)); + + fd = open(file_buf, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (fd < 0 + || write(fd, pgpass_buf, len_pgpass) != len_pgpass + || close(fd) != 0) + { + fprintf(stderr, _("%s: could not write file \"%s\": %s\n"), + progname, file_buf, strerror(errno)); + exit(2); + } +} + +/* * Prepare environment variables for running regression tests */ static void @@ -2152,11 +2262,16 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc } } + /* generate a password for the bootstrap superuser */ + initialize_password(); + /* initdb */ header(_("initializing database system")); snprintf(buf, sizeof(buf), - SYSTEMQUOTE "\"%s/initdb\" -D \"%s/data\" -L \"%s\" --noclean --nosync%s%s > \"%s/log/initdb.log\" 2>&1" SYSTEMQUOTE, - bindir, temp_install, datadir, + SYSTEMQUOTE "\"%s/initdb\" -D \"%s/data\" -L \"%s\" " + "--auth=md5 --pwfile=\"%s\" --noclean --nosync%s%s " + "> \"%s/log/initdb.log\" 2>&1" SYSTEMQUOTE, + bindir, temp_install, datadir, server_pwfile, debug ? " --debug" : "", nolocale ? " --no-locale" : "", outputdir); diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 308a4b4..36369e2 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -68,7 +68,8 @@ sub mkvcbuild chklocale.c crypt.c fls.c fseeko.c getrusage.c inet_aton.c random.c 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 + pgcheckdir.c pg_crc.c pgencode.c pgmkdirp.c + pgrand.c pgsleep.c pgstrcasecmp.c pqsignal.c qsort.c qsort_arg.c quotes.c sprompt.c tar.c thread.c getopt.c getopt_long.c dirent.c win32env.c win32error.c win32setlocale.c); @@ -375,6 +376,8 @@ sub mkvcbuild $pgreceivexlog->AddFile('src\bin\pg_basebackup\pg_receivexlog.c'); $pgreceivexlog->AddLibrary('ws2_32.lib'); + my $pggenpassword = AddSimpleFrontend('pg_genpassword'); + my $pgconfig = AddSimpleFrontend('pg_config'); my $pgcontrol = AddSimpleFrontend('pg_controldata'); diff --git a/src/tools/msvc/vcregress.pl b/src/tools/msvc/vcregress.pl index 447b4a1..8a6b9e9 100644 --- a/src/tools/msvc/vcregress.pl +++ b/src/tools/msvc/vcregress.pl @@ -7,7 +7,9 @@ use strict; our $config; use Cwd; +use Fcntl qw(:DEFAULT :mode); use File::Copy; +use IO::File; use Install qw(Install); @@ -236,6 +238,21 @@ sub contribcheck exit $mstat if $mstat; } +# Write the given content to a given file, creating the file as accessible to +# its owner alone. Die on any error. +sub write_sensitive_file +{ + my ($file, $content) = @_; + + my $fh = + IO::File->new($file, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR) + || die qq{Could not open file "$file" for writing: $!}; + $fh->print($content) + || die qq{Could not write to file "$file": $!}; + $fh->close + || die qq{Could not close file "$file": $!}; +} + sub upgradecheck { my $status; @@ -259,12 +276,23 @@ sub upgradecheck my ($bindir, $libdir, $oldsrc, $newsrc) = ("$tmp_install/bin", "$tmp_install/lib", $topdir, $topdir); $ENV{PATH} = "$bindir;$ENV{PATH}"; + + # Generate a password for the bootstrap superuser. Since pg_genpassword + # is expected to work on all supported Windows configurations, don't + # bother supporting the PGTESTPWFILE override. + my $pwfile = "$tmp_root/superuser_password"; + $ENV{PGPASSFILE} = "$tmp_root/pgpass"; + my $password = `pg_genpassword`; + die "Failed to generate strong password" if $? || length $password < 32; + write_sensitive_file($pwfile, $password); + write_sensitive_file($ENV{PGPASSFILE}, "*:*:*:*:$password"); + my $data = "$tmp_root/data"; $ENV{PGDATA} = "$data.old"; 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; + system('initdb', '--auth=md5', "--pwfile=$pwfile") == 0 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"; @@ -278,7 +306,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; + system('initdb', '--auth=md5', "--pwfile=$pwfile") == 0 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;