diff options
-rw-r--r-- | contrib/pg_upgrade/dump.c | 15 | ||||
-rw-r--r-- | contrib/pg_upgrade/pg_upgrade.c | 15 | ||||
-rw-r--r-- | contrib/pg_upgrade/pg_upgrade.h | 4 | ||||
-rw-r--r-- | contrib/pg_upgrade/server.c | 50 | ||||
-rw-r--r-- | contrib/pg_upgrade/test.sh | 17 | ||||
-rw-r--r-- | contrib/pg_upgrade/util.c | 204 | ||||
-rw-r--r-- | contrib/pg_upgrade/version.c | 10 | ||||
-rw-r--r-- | contrib/pg_upgrade/version_old_8_3.c | 34 | ||||
-rw-r--r-- | src/bin/pg_basebackup/streamutil.c | 9 | ||||
-rw-r--r-- | src/bin/pg_dump/dumputils.c | 204 | ||||
-rw-r--r-- | src/bin/pg_dump/dumputils.h | 3 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_backup.h | 2 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_backup_archiver.c | 34 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_backup_db.c | 9 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dumpall.c | 162 | ||||
-rw-r--r-- | src/bin/psql/command.c | 15 | ||||
-rw-r--r-- | src/bin/scripts/clusterdb.c | 9 | ||||
-rw-r--r-- | src/bin/scripts/reindexdb.c | 14 | ||||
-rw-r--r-- | src/bin/scripts/vacuumdb.c | 10 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 6 | ||||
-rw-r--r-- | src/tools/msvc/vcregress.pl | 41 |
21 files changed, 665 insertions, 202 deletions
diff --git a/contrib/pg_upgrade/dump.c b/contrib/pg_upgrade/dump.c index 633d5e678f5..7a0e3421486 100644 --- a/contrib/pg_upgrade/dump.c +++ b/contrib/pg_upgrade/dump.c @@ -46,6 +46,15 @@ generate_old_dump(void) char sql_file_name[MAXPGPATH], log_file_name[MAXPGPATH]; DbInfo *old_db = &old_cluster.dbarr.dbs[dbnum]; + PQExpBufferData connstr, + escaped_connstr; + + initPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, old_db->db_name); + initPQExpBuffer(&escaped_connstr); + appendShellString(&escaped_connstr, connstr.data); + termPQExpBuffer(&connstr); pg_log(PG_STATUS, "%s", old_db->db_name); snprintf(sql_file_name, sizeof(sql_file_name), DB_DUMP_FILE_MASK, old_db->db_oid); @@ -53,10 +62,12 @@ generate_old_dump(void) parallel_exec_prog(log_file_name, NULL, "\"%s/pg_dump\" %s --schema-only --quote-all-identifiers " - "--binary-upgrade --format=custom %s --file=\"%s\" \"%s\"", + "--binary-upgrade --format=custom %s --file=\"%s\" %s", new_cluster.bindir, cluster_conn_opts(&old_cluster), log_opts.verbose ? "--verbose" : "", - sql_file_name, old_db->db_name); + sql_file_name, escaped_connstr.data); + + termPQExpBuffer(&escaped_connstr); } /* reap all children */ diff --git a/contrib/pg_upgrade/pg_upgrade.c b/contrib/pg_upgrade/pg_upgrade.c index 9b757129a88..ded26c4035a 100644 --- a/contrib/pg_upgrade/pg_upgrade.c +++ b/contrib/pg_upgrade/pg_upgrade.c @@ -498,6 +498,15 @@ create_new_objects(void) char sql_file_name[MAXPGPATH], log_file_name[MAXPGPATH]; DbInfo *old_db = &old_cluster.dbarr.dbs[dbnum]; + PQExpBufferData connstr, + escaped_connstr; + + initPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, old_db->db_name); + initPQExpBuffer(&escaped_connstr); + appendShellString(&escaped_connstr, connstr.data); + termPQExpBuffer(&connstr); pg_log(PG_STATUS, "%s", old_db->db_name); snprintf(sql_file_name, sizeof(sql_file_name), DB_DUMP_FILE_MASK, old_db->db_oid); @@ -509,11 +518,13 @@ create_new_objects(void) */ parallel_exec_prog(log_file_name, NULL, - "\"%s/pg_restore\" %s --exit-on-error --verbose --dbname \"%s\" \"%s\"", + "\"%s/pg_restore\" %s --exit-on-error --verbose --dbname %s \"%s\"", new_cluster.bindir, cluster_conn_opts(&new_cluster), - old_db->db_name, + escaped_connstr.data, sql_file_name); + + termPQExpBuffer(&escaped_connstr); } /* reap all children */ diff --git a/contrib/pg_upgrade/pg_upgrade.h b/contrib/pg_upgrade/pg_upgrade.h index be087cf8cd3..4b6536a35e1 100644 --- a/contrib/pg_upgrade/pg_upgrade.h +++ b/contrib/pg_upgrade/pg_upgrade.h @@ -11,6 +11,7 @@ #include <sys/time.h> #include "libpq-fe.h" +#include "pqexpbuffer.h" /* Use port in the private/dynamic port number range */ #define DEF_PGUPORT 50432 @@ -440,6 +441,9 @@ void check_pghost_envvar(void); /* util.c */ char *quote_identifier(const char *s); +extern void appendShellString(PQExpBuffer buf, const char *str); +extern void appendConnStrVal(PQExpBuffer buf, const char *str); +extern void appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname); int get_user_info(char **user_name); void check_ok(void); void diff --git a/contrib/pg_upgrade/server.c b/contrib/pg_upgrade/server.c index c55f4b77458..19fbd324c66 100644 --- a/contrib/pg_upgrade/server.c +++ b/contrib/pg_upgrade/server.c @@ -51,18 +51,25 @@ connectToServer(ClusterInfo *cluster, const char *db_name) static PGconn * get_db_conn(ClusterInfo *cluster, const char *db_name) { - char conn_opts[2 * NAMEDATALEN + MAXPGPATH + 100]; + PQExpBufferData conn_opts; + PGconn *conn; + /* Build connection string with proper quoting */ + initPQExpBuffer(&conn_opts); + appendPQExpBufferStr(&conn_opts, "dbname="); + appendConnStrVal(&conn_opts, db_name); + appendPQExpBufferStr(&conn_opts, " user="); + appendConnStrVal(&conn_opts, os_info.user); + appendPQExpBuffer(&conn_opts, " port=%d", cluster->port); if (cluster->sockdir) - snprintf(conn_opts, sizeof(conn_opts), - "dbname = '%s' user = '%s' host = '%s' port = %d", - db_name, os_info.user, cluster->sockdir, cluster->port); - else - snprintf(conn_opts, sizeof(conn_opts), - "dbname = '%s' user = '%s' port = %d", - db_name, os_info.user, cluster->port); + { + appendPQExpBufferStr(&conn_opts, " host="); + appendConnStrVal(&conn_opts, cluster->sockdir); + } - return PQconnectdb(conn_opts); + conn = PQconnectdb(conn_opts.data); + termPQExpBuffer(&conn_opts); + return conn; } @@ -74,23 +81,28 @@ get_db_conn(ClusterInfo *cluster, const char *db_name) * sets, but the utilities we need aren't very consistent about the treatment * of database name options, so we leave that out. * - * Note result is in static storage, so use it right away. + * Result is valid until the next call to this function. */ char * cluster_conn_opts(ClusterInfo *cluster) { - static char conn_opts[MAXPGPATH + NAMEDATALEN + 100]; + static PQExpBuffer buf; - if (cluster->sockdir) - snprintf(conn_opts, sizeof(conn_opts), - "--host \"%s\" --port %d --username \"%s\"", - cluster->sockdir, cluster->port, os_info.user); + if (buf == NULL) + buf = createPQExpBuffer(); else - snprintf(conn_opts, sizeof(conn_opts), - "--port %d --username \"%s\"", - cluster->port, os_info.user); + resetPQExpBuffer(buf); + + if (cluster->sockdir) + { + appendPQExpBufferStr(buf, "--host "); + appendShellString(buf, cluster->sockdir); + appendPQExpBufferChar(buf, ' '); + } + appendPQExpBuffer(buf, "--port %d --username ", cluster->port); + appendShellString(buf, os_info.user); - return conn_opts; + return buf->data; } diff --git a/contrib/pg_upgrade/test.sh b/contrib/pg_upgrade/test.sh index 5fdf8057f1e..2072011bfc3 100644 --- a/contrib/pg_upgrade/test.sh +++ b/contrib/pg_upgrade/test.sh @@ -155,6 +155,20 @@ set -x standard_initdb "$oldbindir"/initdb $oldbindir/pg_ctl start -l "$logdir/postmaster1.log" -o "$POSTMASTER_OPTS" -w + +# Create databases with names covering the ASCII bytes other than NUL, BEL, +# LF, or CR. BEL would ring the terminal bell in the course of this test, and +# it is not otherwise a special case. PostgreSQL doesn't support the rest. +dbname1=`awk 'BEGIN { for (i= 1; i < 46; i++) + if (i != 7 && i != 10 && i != 13) printf "%c", i }' </dev/null` +# Exercise backslashes adjacent to double quotes, a Windows special case. +dbname1='\"\'$dbname1'\\"\\\' +dbname2=`awk 'BEGIN { for (i = 46; i < 91; i++) printf "%c", i }' </dev/null` +dbname3=`awk 'BEGIN { for (i = 91; i < 128; i++) printf "%c", i }' </dev/null` +createdb "$dbname1" || createdb_status=$? +createdb "$dbname2" || createdb_status=$? +createdb "$dbname3" || createdb_status=$? + if "$MAKE" -C "$oldsrc" installcheck; then pg_dumpall -f "$temp_root"/dump1.sql || pg_dumpall1_status=$? if [ "$newsrc" != "$oldsrc" ]; then @@ -180,6 +194,9 @@ else make_installcheck_status=$? fi $oldbindir/pg_ctl -m fast stop +if [ -n "$createdb_status" ]; then + exit 1 +fi if [ -n "$make_installcheck_status" ]; then exit 1 fi diff --git a/contrib/pg_upgrade/util.c b/contrib/pg_upgrade/util.c index 1e5f3652cad..f37e2308ffc 100644 --- a/contrib/pg_upgrade/util.c +++ b/contrib/pg_upgrade/util.c @@ -181,6 +181,210 @@ quote_identifier(const char *s) /* + * Append the given string to the shell command being built in the buffer, + * with suitable shell-style quoting to create exactly one argument. + * + * Forbid LF or CR characters, which have scant practical use beyond designing + * security breaches. The Windows command shell is unusable as a conduit for + * arguments containing LF or CR characters. A future major release should + * reject those characters in CREATE ROLE and CREATE DATABASE, because use + * there eventually leads to errors here. + */ +void +appendShellString(PQExpBuffer buf, const char *str) +{ + const char *p; + +#ifndef WIN32 + appendPQExpBufferChar(buf, '\''); + for (p = str; *p; p++) + { + if (*p == '\n' || *p == '\r') + { + fprintf(stderr, + _("shell command argument contains a newline or carriage return: \"%s\"\n"), + str); + exit(EXIT_FAILURE); + } + + if (*p == '\'') + appendPQExpBufferStr(buf, "'\"'\"'"); + else + appendPQExpBufferChar(buf, *p); + } + appendPQExpBufferChar(buf, '\''); +#else /* WIN32 */ + int backslash_run_length = 0; + + /* + * A Windows system() argument experiences two layers of interpretation. + * First, cmd.exe interprets the string. Its behavior is undocumented, + * but a caret escapes any byte except LF or CR that would otherwise have + * special meaning. Handling of a caret before LF or CR differs between + * "cmd.exe /c" and other modes, and it is unusable here. + * + * Second, the new process parses its command line to construct argv (see + * https://msdn.microsoft.com/en-us/library/17w5ykft.aspx). This treats + * backslash-double quote sequences specially. + */ + appendPQExpBufferStr(buf, "^\""); + for (p = str; *p; p++) + { + if (*p == '\n' || *p == '\r') + { + fprintf(stderr, + _("shell command argument contains a newline or carriage return: \"%s\"\n"), + str); + exit(EXIT_FAILURE); + } + + /* Change N backslashes before a double quote to 2N+1 backslashes. */ + if (*p == '"') + { + while (backslash_run_length) + { + appendPQExpBufferStr(buf, "^\\"); + backslash_run_length--; + } + appendPQExpBufferStr(buf, "^\\"); + } + else if (*p == '\\') + backslash_run_length++; + else + backslash_run_length = 0; + + /* + * Decline to caret-escape the most mundane characters, to ease + * debugging and lest we approach the command length limit. + */ + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9'))) + appendPQExpBufferChar(buf, '^'); + appendPQExpBufferChar(buf, *p); + } + + /* + * Change N backslashes at end of argument to 2N backslashes, because they + * precede the double quote that terminates the argument. + */ + while (backslash_run_length) + { + appendPQExpBufferStr(buf, "^\\"); + backslash_run_length--; + } + appendPQExpBufferStr(buf, "^\""); +#endif /* WIN32 */ +} + + +/* + * Append the given string to the buffer, with suitable quoting for passing + * the string as a value, in a keyword/pair value in a libpq connection + * string + */ +void +appendConnStrVal(PQExpBuffer buf, const char *str) +{ + const char *s; + bool needquotes; + + /* + * If the string is one or more plain ASCII characters, no need to quote + * it. This is quite conservative, but better safe than sorry. + */ + needquotes = true; + for (s = str; *s; s++) + { + if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9') || *s == '_' || *s == '.')) + { + needquotes = true; + break; + } + needquotes = false; + } + + if (needquotes) + { + appendPQExpBufferChar(buf, '\''); + while (*str) + { + /* ' and \ must be escaped by to \' and \\ */ + if (*str == '\'' || *str == '\\') + appendPQExpBufferChar(buf, '\\'); + + appendPQExpBufferChar(buf, *str); + str++; + } + appendPQExpBufferChar(buf, '\''); + } + else + appendPQExpBufferStr(buf, str); +} + + +/* + * Append a psql meta-command that connects to the given database with the + * then-current connection's user, host and port. + */ +void +appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname) +{ + const char *s; + bool complex; + + /* + * If the name is plain ASCII characters, emit a trivial "\connect "foo"". + * For other names, even many not technically requiring it, skip to the + * general case. No database has a zero-length name. + */ + complex = false; + for (s = dbname; *s; s++) + { + if (*s == '\n' || *s == '\r') + { + fprintf(stderr, + _("database name contains a newline or carriage return: \"%s\"\n"), + dbname); + exit(EXIT_FAILURE); + } + + if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9') || *s == '_' || *s == '.')) + { + complex = true; + } + } + + appendPQExpBufferStr(buf, "\\connect "); + if (complex) + { + PQExpBufferData connstr; + + initPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, dbname); + + appendPQExpBuffer(buf, "-reuse-previous=on "); + + /* + * As long as the name does not contain a newline, SQL identifier + * quoting satisfies the psql meta-command parser. Prefer not to + * involve psql-interpreted single quotes, which behaved differently + * before PostgreSQL 9.2. + */ + appendPQExpBufferStr(buf, quote_identifier(connstr.data)); + + termPQExpBuffer(&connstr); + } + else + appendPQExpBufferStr(buf, quote_identifier(dbname)); + appendPQExpBufferChar(buf, '\n'); +} + + +/* * get_user_info() * (copied from initdb.c) find the current user */ diff --git a/contrib/pg_upgrade/version.c b/contrib/pg_upgrade/version.c index 5fe7ec45202..01f32e34987 100644 --- a/contrib/pg_upgrade/version.c +++ b/contrib/pg_upgrade/version.c @@ -48,10 +48,16 @@ new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster, bool check_mode) found = true; if (!check_mode) { + PQExpBufferData connectbuf; + if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL) pg_log(PG_FATAL, "could not open file \"%s\": %s\n", output_path, getErrorText(errno)); - fprintf(script, "\\connect %s\n", - quote_identifier(active_db->db_name)); + + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, active_db->db_name); + fputs(connectbuf.data, script); + termPQExpBuffer(&connectbuf); + fprintf(script, "SELECT pg_catalog.lo_create(t.loid)\n" "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) AS t;\n"); diff --git a/contrib/pg_upgrade/version_old_8_3.c b/contrib/pg_upgrade/version_old_8_3.c index e244dcf9ea0..c4eb6f62252 100644 --- a/contrib/pg_upgrade/version_old_8_3.c +++ b/contrib/pg_upgrade/version_old_8_3.c @@ -367,8 +367,13 @@ old_8_3_rebuild_tsvector_tables(ClusterInfo *cluster, bool check_mode) pg_log(PG_FATAL, "could not open file \"%s\": %s\n", output_path, getErrorText(errno)); if (!db_used) { - fprintf(script, "\\connect %s\n\n", - quote_identifier(active_db->db_name)); + PQExpBufferData connectbuf; + + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, active_db->db_name); + appendPQExpBufferChar(&connectbuf, '\n'); + fputs(connectbuf.data, script); + termPQExpBuffer(&connectbuf); db_used = true; } @@ -483,8 +488,12 @@ old_8_3_invalidate_hash_gin_indexes(ClusterInfo *cluster, bool check_mode) pg_log(PG_FATAL, "could not open file \"%s\": %s\n", output_path, getErrorText(errno)); if (!db_used) { - fprintf(script, "\\connect %s\n", - quote_identifier(active_db->db_name)); + PQExpBufferData connectbuf; + + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, active_db->db_name); + fputs(connectbuf.data, script); + termPQExpBuffer(&connectbuf); db_used = true; } fprintf(script, "REINDEX INDEX %s.%s;\n", @@ -602,8 +611,12 @@ old_8_3_invalidate_bpchar_pattern_ops_indexes(ClusterInfo *cluster, pg_log(PG_FATAL, "could not open file \"%s\": %s\n", output_path, getErrorText(errno)); if (!db_used) { - fprintf(script, "\\connect %s\n", - quote_identifier(active_db->db_name)); + PQExpBufferData connectbuf; + + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, active_db->db_name); + fputs(connectbuf.data, script); + termPQExpBuffer(&connectbuf); db_used = true; } fprintf(script, "REINDEX INDEX %s.%s;\n", @@ -724,8 +737,13 @@ old_8_3_create_sequence_script(ClusterInfo *cluster) pg_log(PG_FATAL, "could not open file \"%s\": %s\n", output_path, getErrorText(errno)); if (!db_used) { - fprintf(script, "\\connect %s\n\n", - quote_identifier(active_db->db_name)); + PQExpBufferData connectbuf; + + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, active_db->db_name); + appendPQExpBufferChar(&connectbuf, '\n'); + fputs(connectbuf.data, script); + termPQExpBuffer(&connectbuf); db_used = true; } diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 1dfb80f4a72..23e57972589 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -49,6 +49,9 @@ GetConnection(void) /* * Merge the connection info inputs given in form of connection string, * options and default values (dbname=replication, replication=true, etc.) + * Explicitly discard any dbname value in the connection string; + * otherwise, PQconnectdbParams() would interpret that value as being + * itself a connection string. */ i = 0; if (connection_string) @@ -62,7 +65,8 @@ GetConnection(void) for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++) { - if (conn_opt->val != NULL && conn_opt->val[0] != '\0') + if (conn_opt->val != NULL && conn_opt->val[0] != '\0' && + strcmp(conn_opt->keyword, "dbname") != 0) argcount++; } @@ -71,7 +75,8 @@ GetConnection(void) for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++) { - if (conn_opt->val != NULL && conn_opt->val[0] != '\0') + if (conn_opt->val != NULL && conn_opt->val[0] != '\0' && + strcmp(conn_opt->keyword, "dbname") != 0) { keywords[i] = conn_opt->keyword; values[i] = conn_opt->val; diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index b812069a40c..1ff73722773 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -340,6 +340,210 @@ appendStringLiteralDQ(PQExpBuffer buf, const char *str, const char *dqprefix) /* + * Append the given string to the shell command being built in the buffer, + * with suitable shell-style quoting to create exactly one argument. + * + * Forbid LF or CR characters, which have scant practical use beyond designing + * security breaches. The Windows command shell is unusable as a conduit for + * arguments containing LF or CR characters. A future major release should + * reject those characters in CREATE ROLE and CREATE DATABASE, because use + * there eventually leads to errors here. + */ +void +appendShellString(PQExpBuffer buf, const char *str) +{ + const char *p; + +#ifndef WIN32 + appendPQExpBufferChar(buf, '\''); + for (p = str; *p; p++) + { + if (*p == '\n' || *p == '\r') + { + fprintf(stderr, + _("shell command argument contains a newline or carriage return: \"%s\"\n"), + str); + exit(EXIT_FAILURE); + } + + if (*p == '\'') + appendPQExpBufferStr(buf, "'\"'\"'"); + else + appendPQExpBufferChar(buf, *p); + } + appendPQExpBufferChar(buf, '\''); +#else /* WIN32 */ + int backslash_run_length = 0; + + /* + * A Windows system() argument experiences two layers of interpretation. + * First, cmd.exe interprets the string. Its behavior is undocumented, + * but a caret escapes any byte except LF or CR that would otherwise have + * special meaning. Handling of a caret before LF or CR differs between + * "cmd.exe /c" and other modes, and it is unusable here. + * + * Second, the new process parses its command line to construct argv (see + * https://msdn.microsoft.com/en-us/library/17w5ykft.aspx). This treats + * backslash-double quote sequences specially. + */ + appendPQExpBufferStr(buf, "^\""); + for (p = str; *p; p++) + { + if (*p == '\n' || *p == '\r') + { + fprintf(stderr, + _("shell command argument contains a newline or carriage return: \"%s\"\n"), + str); + exit(EXIT_FAILURE); + } + + /* Change N backslashes before a double quote to 2N+1 backslashes. */ + if (*p == '"') + { + while (backslash_run_length) + { + appendPQExpBufferStr(buf, "^\\"); + backslash_run_length--; + } + appendPQExpBufferStr(buf, "^\\"); + } + else if (*p == '\\') + backslash_run_length++; + else + backslash_run_length = 0; + + /* + * Decline to caret-escape the most mundane characters, to ease + * debugging and lest we approach the command length limit. + */ + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9'))) + appendPQExpBufferChar(buf, '^'); + appendPQExpBufferChar(buf, *p); + } + + /* + * Change N backslashes at end of argument to 2N backslashes, because they + * precede the double quote that terminates the argument. + */ + while (backslash_run_length) + { + appendPQExpBufferStr(buf, "^\\"); + backslash_run_length--; + } + appendPQExpBufferStr(buf, "^\""); +#endif /* WIN32 */ +} + + +/* + * Append the given string to the buffer, with suitable quoting for passing + * the string as a value, in a keyword/pair value in a libpq connection + * string + */ +void +appendConnStrVal(PQExpBuffer buf, const char *str) +{ + const char *s; + bool needquotes; + + /* + * If the string is one or more plain ASCII characters, no need to quote + * it. This is quite conservative, but better safe than sorry. + */ + needquotes = true; + for (s = str; *s; s++) + { + if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9') || *s == '_' || *s == '.')) + { + needquotes = true; + break; + } + needquotes = false; + } + + if (needquotes) + { + appendPQExpBufferChar(buf, '\''); + while (*str) + { + /* ' and \ must be escaped by to \' and \\ */ + if (*str == '\'' || *str == '\\') + appendPQExpBufferChar(buf, '\\'); + + appendPQExpBufferChar(buf, *str); + str++; + } + appendPQExpBufferChar(buf, '\''); + } + else + appendPQExpBufferStr(buf, str); +} + + +/* + * Append a psql meta-command that connects to the given database with the + * then-current connection's user, host and port. + */ +void +appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname) +{ + const char *s; + bool complex; + + /* + * If the name is plain ASCII characters, emit a trivial "\connect "foo"". + * For other names, even many not technically requiring it, skip to the + * general case. No database has a zero-length name. + */ + complex = false; + for (s = dbname; *s; s++) + { + if (*s == '\n' || *s == '\r') + { + fprintf(stderr, + _("database name contains a newline or carriage return: \"%s\"\n"), + dbname); + exit(EXIT_FAILURE); + } + + if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9') || *s == '_' || *s == '.')) + { + complex = true; + } + } + + appendPQExpBufferStr(buf, "\\connect "); + if (complex) + { + PQExpBufferData connstr; + + initPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, dbname); + + appendPQExpBuffer(buf, "-reuse-previous=on "); + + /* + * As long as the name does not contain a newline, SQL identifier + * quoting satisfies the psql meta-command parser. Prefer not to + * involve psql-interpreted single quotes, which behaved differently + * before PostgreSQL 9.2. + */ + appendPQExpBufferStr(buf, fmtId(connstr.data)); + + termPQExpBuffer(&connstr); + } + else + appendPQExpBufferStr(buf, fmtId(dbname)); + appendPQExpBufferChar(buf, '\n'); +} + + +/* * Convert a bytea value (presented as raw bytes) to an SQL string literal * and append it to the given buffer. We assume the specified * standard_conforming_strings setting. diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index 356e8ebd32d..3e7a1da5efc 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -47,6 +47,9 @@ extern void appendStringLiteralDQ(PQExpBuffer buf, const char *str, extern void appendByteaLiteral(PQExpBuffer buf, const unsigned char *str, size_t length, bool std_strings); +extern void appendShellString(PQExpBuffer buf, const char *str); +extern void appendConnStrVal(PQExpBuffer buf, const char *str); +extern void appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname); extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems); extern bool buildACLCommands(const char *name, const char *subname, const char *type, const char *acls, const char *owner, diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index b456f959692..a89a8bde5da 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -136,7 +136,7 @@ typedef struct _restoreOptions SimpleStringList tableNames; int useDB; - char *dbname; + char *dbname; /* subject to expand_dbname */ char *pgport; char *pghost; char *username; diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index a1c86e791a7..f156325d03e 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -595,9 +595,16 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, /* If we created a DB, connect to it... */ if (strcmp(te->desc, "DATABASE") == 0) { + PQExpBufferData connstr; + + initPQExpBuffer(&connstr); + appendPQExpBufferStr(&connstr, "dbname="); + appendConnStrVal(&connstr, te->tag); + /* Abandon struct, but keep its buffer until process exit. */ + ahlog(AH, 1, "connecting to new database \"%s\"\n", te->tag); _reconnectToDB(AH, te->tag); - ropt->dbname = pg_strdup(te->tag); + ropt->dbname = connstr.data; } } @@ -2706,12 +2713,17 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) ReconnectToServer(AH, dbname, NULL); else { - PQExpBuffer qry = createPQExpBuffer(); + if (dbname) + { + PQExpBufferData connectbuf; - appendPQExpBuffer(qry, "\\connect %s\n\n", - dbname ? fmtId(dbname) : "-"); - ahprintf(AH, "%s", qry->data); - destroyPQExpBuffer(qry); + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, dbname); + ahprintf(AH, "%s\n", connectbuf.data); + termPQExpBuffer(&connectbuf); + } + else + ahprintf(AH, "%s\n", "\\connect -\n"); } /* @@ -4217,7 +4229,7 @@ CloneArchive(ArchiveHandle *AH) } else { - char *dbname; + PQExpBufferData connstr; char *pghost; char *pgport; char *username; @@ -4230,14 +4242,18 @@ CloneArchive(ArchiveHandle *AH) * because all just return a pointer and do not actually send/receive * any data to/from the database. */ - dbname = PQdb(AH->connection); + initPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, PQdb(AH->connection)); pghost = PQhost(AH->connection); pgport = PQport(AH->connection); username = PQuser(AH->connection); /* this also sets clone->connection */ - ConnectDatabase((Archive *) clone, dbname, pghost, pgport, username, TRI_NO); + ConnectDatabase((Archive *) clone, connstr.data, + pghost, pgport, username, TRI_NO); + termPQExpBuffer(&connstr); /* setupDumpWorker will fix up connection state */ } diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c index 00a0df1e1bf..ad76cdbbe95 100644 --- a/src/bin/pg_dump/pg_backup_db.c +++ b/src/bin/pg_dump/pg_backup_db.c @@ -111,6 +111,7 @@ ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username) static PGconn * _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) { + PQExpBufferData connstr; PGconn *newConn; const char *newdb; const char *newuser; @@ -139,6 +140,10 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) exit_horribly(modulename, "out of memory\n"); } + initPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, newdb); + do { const char *keywords[7]; @@ -153,7 +158,7 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) keywords[3] = "password"; values[3] = password; keywords[4] = "dbname"; - values[4] = newdb; + values[4] = connstr.data; keywords[5] = "fallback_application_name"; values[5] = progname; keywords[6] = NULL; @@ -205,6 +210,8 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) if (password) free(password); + termPQExpBuffer(&connstr); + /* check for version mismatch */ _check_database_version(AH); diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 379417fb66e..0e9e029c6c8 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -49,8 +49,6 @@ static void makeAlterConfigCommand(PGconn *conn, const char *arrayitem, const char *name2); static void dumpDatabases(PGconn *conn); static void dumpTimestamp(char *msg); -static void appendShellString(PQExpBuffer buf, const char *str); -static void appendConnStrVal(PQExpBuffer buf, const char *str); static int runPgDump(const char *dbname); static void buildShSecLabels(PGconn *conn, const char *catalog_name, @@ -1399,7 +1397,7 @@ dumpCreateDB(PGconn *conn) fdbname, fmtId(dbtablespace)); /* connect to original database */ - appendPQExpBuffer(buf, "\\connect %s\n", fdbname); + appendPsqlMetaConnect(buf, dbname); } if (binary_upgrade) @@ -1627,11 +1625,15 @@ dumpDatabases(PGconn *conn) int ret; char *dbname = PQgetvalue(res, i, 0); + PQExpBufferData connectbuf; if (verbose) fprintf(stderr, _("%s: dumping database \"%s\"...\n"), progname, dbname); - fprintf(OPF, "\\connect %s\n\n", fmtId(dbname)); + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, dbname); + fprintf(OPF, "%s\n", connectbuf.data); + termPQExpBuffer(&connectbuf); /* * Restore will need to write to the target cluster. This connection @@ -1789,7 +1791,9 @@ connectDatabase(const char *dbname, const char *connection_string, /* * Merge the connection info inputs given in form of connection string - * and other options. + * and other options. Explicitly discard any dbname value in the + * connection string; otherwise, PQconnectdbParams() would interpret + * that value as being itself a connection string. */ if (connection_string) { @@ -1802,7 +1806,8 @@ connectDatabase(const char *dbname, const char *connection_string, for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++) { - if (conn_opt->val != NULL && conn_opt->val[0] != '\0') + if (conn_opt->val != NULL && conn_opt->val[0] != '\0' && + strcmp(conn_opt->keyword, "dbname") != 0) argcount++; } @@ -1811,7 +1816,8 @@ connectDatabase(const char *dbname, const char *connection_string, for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++) { - if (conn_opt->val != NULL && conn_opt->val[0] != '\0') + if (conn_opt->val != NULL && conn_opt->val[0] != '\0' && + strcmp(conn_opt->keyword, "dbname") != 0) { keywords[i] = conn_opt->keyword; values[i] = conn_opt->val; @@ -2068,145 +2074,3 @@ dumpTimestamp(char *msg) localtime(&now)) != 0) fprintf(OPF, "-- %s %s\n\n", msg, buf); } - - -/* - * Append the given string to the buffer, with suitable quoting for passing - * the string as a value, in a keyword/pair value in a libpq connection - * string - */ -static void -appendConnStrVal(PQExpBuffer buf, const char *str) -{ - const char *s; - bool needquotes; - - /* - * If the string consists entirely of plain ASCII characters, no need to - * quote it. This is quite conservative, but better safe than sorry. - */ - needquotes = false; - for (s = str; *s; s++) - { - if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || - (*s >= '0' && *s <= '9') || *s == '_' || *s == '.')) - { - needquotes = true; - break; - } - } - - if (needquotes) - { - appendPQExpBufferChar(buf, '\''); - while (*str) - { - /* ' and \ must be escaped by to \' and \\ */ - if (*str == '\'' || *str == '\\') - appendPQExpBufferChar(buf, '\\'); - - appendPQExpBufferChar(buf, *str); - str++; - } - appendPQExpBufferChar(buf, '\''); - } - else - appendPQExpBufferStr(buf, str); -} - -/* - * Append the given string to the shell command being built in the buffer, - * with suitable shell-style quoting to create exactly one argument. - * - * Forbid LF or CR characters, which have scant practical use beyond designing - * security breaches. The Windows command shell is unusable as a conduit for - * arguments containing LF or CR characters. A future major release should - * reject those characters in CREATE ROLE and CREATE DATABASE, because use - * there eventually leads to errors here. - */ -static void -appendShellString(PQExpBuffer buf, const char *str) -{ - const char *p; - -#ifndef WIN32 - appendPQExpBufferChar(buf, '\''); - for (p = str; *p; p++) - { - if (*p == '\n' || *p == '\r') - { - fprintf(stderr, - _("shell command argument contains a newline or carriage return: \"%s\"\n"), - str); - exit(EXIT_FAILURE); - } - - if (*p == '\'') - appendPQExpBuffer(buf, "'\"'\"'"); - else - appendPQExpBufferChar(buf, *p); - } - appendPQExpBufferChar(buf, '\''); -#else /* WIN32 */ - int backslash_run_length = 0; - - /* - * A Windows system() argument experiences two layers of interpretation. - * First, cmd.exe interprets the string. Its behavior is undocumented, - * but a caret escapes any byte except LF or CR that would otherwise have - * special meaning. Handling of a caret before LF or CR differs between - * "cmd.exe /c" and other modes, and it is unusable here. - * - * Second, the new process parses its command line to construct argv (see - * https://msdn.microsoft.com/en-us/library/17w5ykft.aspx). This treats - * backslash-double quote sequences specially. - */ - appendPQExpBufferStr(buf, "^\""); - for (p = str; *p; p++) - { - if (*p == '\n' || *p == '\r') - { - fprintf(stderr, - _("shell command argument contains a newline or carriage return: \"%s\"\n"), - str); - exit(EXIT_FAILURE); - } - - /* Change N backslashes before a double quote to 2N+1 backslashes. */ - if (*p == '"') - { - while (backslash_run_length) - { - appendPQExpBufferStr(buf, "^\\"); - backslash_run_length--; - } - appendPQExpBufferStr(buf, "^\\"); - } - else if (*p == '\\') - backslash_run_length++; - else - backslash_run_length = 0; - - /* - * Decline to caret-escape the most mundane characters, to ease - * debugging and lest we approach the command length limit. - */ - if (!((*p >= 'a' && *p <= 'z') || - (*p >= 'A' && *p <= 'Z') || - (*p >= '0' && *p <= '9'))) - appendPQExpBufferChar(buf, '^'); - appendPQExpBufferChar(buf, *p); - } - - /* - * Change N backslashes at end of argument to 2N backslashes, because they - * precede the double quote that terminates the argument. - */ - while (backslash_run_length) - { - appendPQExpBufferStr(buf, "^\\"); - backslash_run_length--; - } - appendPQExpBufferStr(buf, "^\""); -#endif /* WIN32 */ -} diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index a16b6f326bc..e210d072f1e 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -1576,6 +1576,7 @@ do_connect(enum trivalue reuse_previous_specification, bool keep_password; bool has_connection_string; bool reuse_previous; + PQExpBufferData connstr; if (!o_conn && (!dbname || !user || !host || !port)) { @@ -1639,7 +1640,15 @@ do_connect(enum trivalue reuse_previous_specification, * changes: passwords aren't (usually) database-specific. */ if (!dbname && reuse_previous) - dbname = PQdb(o_conn); + { + initPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, PQdb(o_conn)); + dbname = connstr.data; + /* has_connection_string=true would be a dead store */ + } + else + connstr.data = NULL; /* * If the user asked to be prompted for a password, ask for one now. If @@ -1748,8 +1757,12 @@ do_connect(enum trivalue reuse_previous_specification, } PQfinish(n_conn); + if (connstr.data) + termPQExpBuffer(&connstr); return false; } + if (connstr.data) + termPQExpBuffer(&connstr); /* * Replace the old connection with the new one, and update diff --git a/src/bin/scripts/clusterdb.c b/src/bin/scripts/clusterdb.c index 4481d3f2e64..1bdfe2967c2 100644 --- a/src/bin/scripts/clusterdb.c +++ b/src/bin/scripts/clusterdb.c @@ -229,6 +229,7 @@ cluster_all_databases(bool verbose, const char *maintenance_db, { PGconn *conn; PGresult *result; + PQExpBufferData connstr; int i; conn = connectMaintenanceDatabase(maintenance_db, host, port, username, @@ -236,6 +237,7 @@ cluster_all_databases(bool verbose, const char *maintenance_db, result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo); PQfinish(conn); + initPQExpBuffer(&connstr); for (i = 0; i < PQntuples(result); i++) { char *dbname = PQgetvalue(result, i, 0); @@ -246,10 +248,15 @@ cluster_all_databases(bool verbose, const char *maintenance_db, fflush(stdout); } - cluster_one_database(dbname, verbose, NULL, + resetPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, dbname); + + cluster_one_database(connstr.data, verbose, NULL, host, port, username, prompt_password, progname, echo); } + termPQExpBuffer(&connstr); PQclear(result); } diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c index 780b4df21d8..3c2a8ff0b8d 100644 --- a/src/bin/scripts/reindexdb.c +++ b/src/bin/scripts/reindexdb.c @@ -285,6 +285,7 @@ reindex_all_databases(const char *maintenance_db, { PGconn *conn; PGresult *result; + PQExpBufferData connstr; int i; conn = connectMaintenanceDatabase(maintenance_db, host, port, username, @@ -292,6 +293,7 @@ reindex_all_databases(const char *maintenance_db, result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo); PQfinish(conn); + initPQExpBuffer(&connstr); for (i = 0; i < PQntuples(result); i++) { char *dbname = PQgetvalue(result, i, 0); @@ -302,9 +304,15 @@ reindex_all_databases(const char *maintenance_db, fflush(stdout); } - reindex_one_database(dbname, dbname, "DATABASE", host, port, username, - prompt_password, progname, echo); + resetPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, dbname); + + reindex_one_database(NULL, connstr.data, "DATABASE", host, + port, username, prompt_password, + progname, echo); } + termPQExpBuffer(&connstr); PQclear(result); } @@ -322,7 +330,7 @@ reindex_system_catalogs(const char *dbname, const char *host, const char *port, initPQExpBuffer(&sql); - appendPQExpBuffer(&sql, "REINDEX SYSTEM %s;\n", PQdb(conn)); + appendPQExpBuffer(&sql, "REINDEX SYSTEM %s;\n", fmtId(PQdb(conn))); if (!executeMaintenanceCommand(conn, sql.data, echo)) { diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c index 5ac3222bb6d..3614e71b6a6 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -325,6 +325,7 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool analyze_onl { PGconn *conn; PGresult *result; + PQExpBufferData connstr; int i; conn = connectMaintenanceDatabase(maintenance_db, host, port, @@ -332,6 +333,7 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool analyze_onl result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo); PQfinish(conn); + initPQExpBuffer(&connstr); for (i = 0; i < PQntuples(result); i++) { char *dbname = PQgetvalue(result, i, 0); @@ -342,10 +344,16 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool analyze_onl fflush(stdout); } - vacuum_one_database(dbname, full, verbose, and_analyze, analyze_only, + resetPQExpBuffer(&connstr); + appendPQExpBuffer(&connstr, "dbname="); + appendConnStrVal(&connstr, PQgetvalue(result, i, 0)); + + vacuum_one_database(connstr.data, full, verbose, and_analyze, + analyze_only, freeze, NULL, host, port, username, prompt_password, progname, echo); } + termPQExpBuffer(&connstr); PQclear(result); } diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index aabdc0ffb02..e9e49a950ba 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -4400,7 +4400,11 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage, * of "dbname" keyword is a connection string (as indicated by * recognized_connection_string) then parse and process it, overriding any * previously processed conflicting keywords. Subsequent keywords will take - * precedence, however. + * precedence, however. In-tree programs generally specify expand_dbname=true, + * so command-line arguments naming a database can use a connection string. + * Some code acquires arbitrary database names from known-literal sources like + * PQdb(), PQconninfoParse() and pg_database.datname. When connecting to such + * a database, in-tree code first wraps the name in a connection string. */ static PQconninfoOption * conninfo_array_parse(const char *const * keywords, const char *const * values, diff --git a/src/tools/msvc/vcregress.pl b/src/tools/msvc/vcregress.pl index 5285d974620..f2cdea980b8 100644 --- a/src/tools/msvc/vcregress.pl +++ b/src/tools/msvc/vcregress.pl @@ -251,6 +251,41 @@ sub standard_initdb $ENV{PGDATA}) == 0); } +# This is similar to appendShellString(). Perl system(@args) bypasses +# cmd.exe, so omit the caret escape layer. +sub quote_system_arg +{ + my $arg = shift; + + # Change N >= 0 backslashes before a double quote to 2N+1 backslashes. + $arg =~ s/(\\*)"/${\($1 . $1)}\\"/gs; + + # Change N >= 1 backslashes at end of argument to 2N backslashes. + $arg =~ s/(\\+)$/${\($1 . $1)}/gs; + + # Wrap the whole thing in unescaped double quotes. + return "\"$arg\""; +} + +# Generate a database with a name made of a range of ASCII characters, useful +# for testing pg_upgrade. +sub generate_db +{ + my ($prefix, $from_char, $to_char, $suffix) = @_; + + my $dbname = $prefix; + for my $i ($from_char .. $to_char) + { + next if $i == 7 || $i == 10 || $i == 13; # skip BEL, LF, and CR + $dbname = $dbname . sprintf('%c', $i); + } + $dbname .= $suffix; + + system('createdb', quote_system_arg($dbname)); + my $status = $? >> 8; + exit $status if $status; +} + sub upgradecheck { my $status; @@ -284,6 +319,12 @@ sub upgradecheck print "\nStarting old cluster\n\n"; my @args = ('pg_ctl', 'start', '-l', "$logdir/postmaster1.log", '-w'); system(@args) == 0 or exit 1; + + print "\nCreating databases with names covering most ASCII bytes\n\n"; + generate_db("\\\"\\", 1, 45, "\\\\\"\\\\\\"); + generate_db('', 46, 90, ''); + generate_db('', 91, 127, ''); + print "\nSetting up data for upgrading\n\n"; installcheck(); |