]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Obstruct shell, SQL, and conninfo injection via database and role names.
authorNoah Misch <noah@leadboat.com>
Mon, 8 Aug 2016 14:07:46 +0000 (10:07 -0400)
committerNoah Misch <noah@leadboat.com>
Mon, 8 Aug 2016 14:07:54 +0000 (10:07 -0400)
Due to simplistic quoting and confusion of database names with conninfo
strings, roles with the CREATEDB or CREATEROLE option could escalate to
superuser privileges when a superuser next ran certain maintenance
commands.  The new coding rule for PQconnectdbParams() calls, documented
at conninfo_array_parse(), is to pass expand_dbname=true and wrap
literal database names in a trivial connection string.  Escape
zero-length values in appendConnStrVal().  Back-patch to 9.1 (all
supported versions).

Nathan Bossart, Michael Paquier, and Noah Misch.  Reviewed by Peter
Eisentraut.  Reported by Nathan Bossart.

Security: CVE-2016-5424

21 files changed:
contrib/pg_upgrade/check.c
contrib/pg_upgrade/dump.c
contrib/pg_upgrade/option.c
contrib/pg_upgrade/pg_upgrade.c
contrib/pg_upgrade/pg_upgrade.h
contrib/pg_upgrade/server.c
contrib/pg_upgrade/util.c
contrib/pg_upgrade/version.c
contrib/pg_upgrade/version_old_8_3.c
src/bin/pg_dump/dumputils.c
src/bin/pg_dump/dumputils.h
src/bin/pg_dump/pg_backup.h
src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_backup_db.c
src/bin/pg_dump/pg_dumpall.c
src/bin/psql/command.c
src/bin/scripts/Makefile
src/bin/scripts/clusterdb.c
src/bin/scripts/reindexdb.c
src/bin/scripts/vacuumdb.c
src/interfaces/libpq/fe-connect.c

index 82fbd23035356b95a253f4b9b6e9d0eb2dbdafa1..db15a75022d124cf3f1abc10f336840941d34e26 100644 (file)
@@ -218,9 +218,10 @@ issue_warnings(char *sequence_script_file_name)
                        prep_status("Adjusting sequences");
                        exec_prog(true,
                                          SYSTEMQUOTE "\"%s/psql\" --set ON_ERROR_STOP=on "
-                                         "--no-psqlrc --port %d --username \"%s\" "
+                                         "--no-psqlrc --port %d --username %s "
                                          "-f \"%s\" --dbname template1 >> \"%s\"" SYSTEMQUOTE,
-                                         new_cluster.bindir, new_cluster.port, os_info.user,
+                                         new_cluster.bindir, new_cluster.port,
+                                         os_info.user_shell_arg,
                                          sequence_script_file_name, log_opts.filename);
                        unlink(sequence_script_file_name);
                        check_ok();
index 4ef3e2bd3d1066ce3acba2cc30e878802dd11e25..fef940831990c26de5afc34a81c6302f9b28620d 100644 (file)
@@ -21,10 +21,11 @@ generate_old_dump(void)
         * restores the frozenid's for databases and relations.
         */
        exec_prog(true,
-                         SYSTEMQUOTE "\"%s/pg_dumpall\" --port %d --username \"%s\" "
+                         SYSTEMQUOTE "\"%s/pg_dumpall\" --port %d --username %s "
                          "--schema-only --quote-all-identifiers --binary-upgrade "
                          "-f \"%s/" ALL_DUMP_FILE "\""
-                         SYSTEMQUOTE, new_cluster.bindir, old_cluster.port, os_info.user, os_info.cwd);
+                         SYSTEMQUOTE, new_cluster.bindir, old_cluster.port,
+                         os_info.user_shell_arg, os_info.cwd);
        check_ok();
 }
 
index 735ee6758d131152beb336191ad677c8851f1cba..04cef630e108d5b25f8d3f42b13b984c3307e6df 100644 (file)
@@ -52,6 +52,7 @@ parseCommandLine(int argc, char *argv[])
        int                     option;                 /* Command line option */
        int                     optindex = 0;   /* used by getopt_long */
        int                     os_user_effective_id;
+       PQExpBufferData userbuf;
 
        user_opts.transfer_mode = TRANSFER_MODE_COPY;
 
@@ -211,6 +212,11 @@ parseCommandLine(int argc, char *argv[])
                                                        "old cluster data resides");
        validateDirectoryOption(&new_cluster.pgdata, "NEWDATADIR", "-D",
                                                        "new cluster data resides");
+
+       initPQExpBuffer(&userbuf);
+       appendShellString(&userbuf, os_info.user);
+       /* Abandon struct, but keep its buffer until process exit. */
+       os_info.user_shell_arg = userbuf.data;
 }
 
 
index ad4da56cff2e2b32cda2faf652084d7cf020ed1c..2e73a25f46dfd694c7d5622fee412e668af4e857 100644 (file)
@@ -359,9 +359,9 @@ prepare_new_cluster(void)
         */
        prep_status("Analyzing all rows in the new cluster");
        exec_prog(true,
-                         SYSTEMQUOTE "\"%s/vacuumdb\" --port %d --username \"%s\" "
+                         SYSTEMQUOTE "\"%s/vacuumdb\" --port %d --username %s "
                          "--all --analyze >> \"%s\" 2>&1" SYSTEMQUOTE,
-         new_cluster.bindir, new_cluster.port, os_info.user,
+         new_cluster.bindir, new_cluster.port, os_info.user_shell_arg,
 #ifndef WIN32
          log_opts.filename
 #else
@@ -378,9 +378,9 @@ prepare_new_cluster(void)
         */
        prep_status("Freezing all rows on the new cluster");
        exec_prog(true,
-                         SYSTEMQUOTE "\"%s/vacuumdb\" --port %d --username \"%s\" "
+                         SYSTEMQUOTE "\"%s/vacuumdb\" --port %d --username %s "
                          "--all --freeze >> \"%s\" 2>&1" SYSTEMQUOTE,
-         new_cluster.bindir, new_cluster.port, os_info.user,
+         new_cluster.bindir, new_cluster.port, os_info.user_shell_arg,
 #ifndef WIN32
          log_opts.filename
 #else
@@ -421,9 +421,10 @@ prepare_new_databases(void)
        exec_prog(true,
                          SYSTEMQUOTE "\"%s/psql\" --set ON_ERROR_STOP=on "
        /* --no-psqlrc prevents AUTOCOMMIT=off */
-                         "--no-psqlrc --port %d --username \"%s\" "
+                         "--no-psqlrc --port %d --username %s "
                          "-f \"%s/%s\" --dbname template1 >> \"%s\"" SYSTEMQUOTE,
-                         new_cluster.bindir, new_cluster.port, os_info.user, os_info.cwd,
+                         new_cluster.bindir, new_cluster.port,
+                         os_info.user_shell_arg, os_info.cwd,
                          GLOBALS_DUMP_FILE,
 #ifndef WIN32
                          log_opts.filename
@@ -458,9 +459,10 @@ create_new_objects(void)
        prep_status("Restoring database schema to new cluster");
        exec_prog(true,
                          SYSTEMQUOTE "\"%s/psql\" --set ON_ERROR_STOP=on "
-                         "--no-psqlrc --port %d --username \"%s\" "
+                         "--no-psqlrc --port %d --username %s "
                          "-f \"%s/%s\" --dbname template1 >> \"%s\"" SYSTEMQUOTE,
-                         new_cluster.bindir, new_cluster.port, os_info.user, os_info.cwd,
+                         new_cluster.bindir, new_cluster.port,
+                         os_info.user_shell_arg, os_info.cwd,
                          DB_DUMP_FILE,
 #ifndef WIN32
                          log_opts.filename
index 72e2e6acafc1169d63857185b9571ba727ad0b99..d0de1628ec83e8b5fae2974e1be1ae91635db687 100644 (file)
@@ -14,6 +14,7 @@
 #include <sys/time.h>
 
 #include "libpq-fe.h"
+#include "pqexpbuffer.h"
 
 /* Allocate for null byte */
 #define USER_NAME_SIZE         128
@@ -236,6 +237,7 @@ typedef struct
        const char *progname;           /* complete pathname for this program */
        char       *exec_path;          /* full path to my executable */
        char       *user;                       /* username for clusters */
+       char       *user_shell_arg; /* the same, with shell quoting */
        char            cwd[MAXPGPATH]; /* current working directory, used for output */
        char      **tablespaces;        /* tablespaces */
        int                     num_tablespaces;
@@ -381,6 +383,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           report_status(eLogType type, const char *fmt,...);
index a4c2b6d50d1c7a5e82226bc2f5b678b52187c119..cb4644bbcdf28e4c838e3b0cc9210c3892d62e9f 100644 (file)
@@ -49,13 +49,20 @@ connectToServer(ClusterInfo *cluster, const char *db_name)
 static PGconn *
 get_db_conn(ClusterInfo *cluster, const char *db_name)
 {
-       char            conn_opts[MAXPGPATH];
+       PQExpBufferData conn_opts;
+       PGconn     *conn;
 
-       snprintf(conn_opts, sizeof(conn_opts),
-                        "dbname = '%s' user = '%s' port = %d", db_name, os_info.user,
-                        cluster->port);
+       /* 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);
 
-       return PQconnectdb(conn_opts);
+       conn = PQconnectdb(conn_opts.data);
+       termPQExpBuffer(&conn_opts);
+       return conn;
 }
 
 
index c32cfcae645a72ba8aa2ae483fe0224839a75518..264357c1d7785a958c84a31a200ff0b8144668dc 100644 (file)
@@ -151,6 +151,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
index 958bcbb80fe151ad8eee46533190f754a154bc53..0392d893e88620db44ab0c8658a8021505e440a4 100644 (file)
@@ -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(output_path, "w")) == NULL)
                                        pg_log(PG_FATAL, "could not create necessary file:  %s\n", output_path);
-                               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");
index ffb79adc91a129157feb411dd813600c7546f751..d3c79f4c05bcb56b0d5ad58b42be060976c4b48c 100644 (file)
@@ -368,8 +368,13 @@ old_8_3_rebuild_tsvector_tables(ClusterInfo *cluster, bool check_mode)
                                        pg_log(PG_FATAL, "could not create necessary file:  %s\n", output_path);
                                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;
                                }
 
@@ -488,8 +493,12 @@ old_8_3_invalidate_hash_gin_indexes(ClusterInfo *cluster, bool check_mode)
                                        pg_log(PG_FATAL, "could not create necessary file:  %s\n", output_path);
                                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",
@@ -613,8 +622,12 @@ old_8_3_invalidate_bpchar_pattern_ops_indexes(ClusterInfo *cluster,
                                        pg_log(PG_FATAL, "could not create necessary file:  %s\n", output_path);
                                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",
@@ -740,8 +753,13 @@ old_8_3_create_sequence_script(ClusterInfo *cluster)
                                pg_log(PG_FATAL, "could not create necessary file:  %s\n", output_path);
                        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;
                        }
 
index 734066ccfa36214f323c1b1b59f47d482227aea8..df5a0fb475811f83b81f210a3caa64b053fd8b38 100644 (file)
@@ -330,6 +330,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
index 44d90669b61655c51f44f363d33e057d2c1f088c..7b2706c86146830327d42a43edece31d8960d2c9 100644 (file)
@@ -33,6 +33,9 @@ extern void appendByteaLiteral(PQExpBuffer buf,
                                   const unsigned char *str, size_t length,
                                   bool std_strings);
 extern int     parse_version(const char *versionString);
+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,
index ce12a41ce3ed98b7d795404d06be483b2c7353d8..3b454e211b767286c1eb260a25d7fad33519c783 100644 (file)
@@ -130,7 +130,7 @@ typedef struct _restoreOptions
        char       *triggerNames;
 
        int                     useDB;
-       char       *dbname;
+       char       *dbname;                     /* subject to expand_dbname */
        char       *pgport;
        char       *pghost;
        char       *username;
index 4bb28d528c7645b6a99a0f4223548f34fe203631..3be3ec8e332dd48bc2a49032a3d7638a6db58304 100644 (file)
@@ -544,9 +544,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 = strdup(te->tag);
+                       ropt->dbname = connstr.data;
                }
        }
 
@@ -2625,12 +2632,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");
        }
 
        /*
index 5d4d0d40e765c2ebf5556386c845e2163e7fafcd..fb5796ef1863498261514babcb2561ebd3c78e05 100644 (file)
@@ -121,6 +121,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;
@@ -147,6 +148,10 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
                        die_horribly(AH, modulename, "out of memory\n");
        }
 
+       initPQExpBuffer(&connstr);
+       appendPQExpBuffer(&connstr, "dbname=");
+       appendConnStrVal(&connstr, newdb);
+
        do
        {
 #define PARAMS_ARRAY_SIZE      7
@@ -165,7 +170,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;
@@ -209,6 +214,8 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
 
        AH->savedPassword = password;
 
+       termPQExpBuffer(&connstr);
+
        /* check for version mismatch */
        _check_database_version(AH);
 
index fa186c6c3a1fb3fcf609bf3cc8a1196e58d311f1..ad6b43c55416c96cbf34ec2d45e362ba1d877cb9 100644 (file)
@@ -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 PGconn *connectDatabase(const char *dbname, const char *pghost, const char *pgport,
@@ -1345,8 +1343,9 @@ dumpCreateDB(PGconn *conn)
                                                          fdbname, fmtId(dbtablespace));
 
                        /* connect to original database */
-                       appendPQExpBuffer(buf, "%s\\connect %s\n",
-                               binary_upgrade ? " " : "", fdbname);
+                       if (binary_upgrade)
+                               appendPQExpBufferChar(buf, ' ');
+                       appendPsqlMetaConnect(buf, dbname);
                }
 
                if (binary_upgrade)
@@ -1569,11 +1568,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
@@ -1880,145 +1883,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 */
-}
index 761f4cee150fed8694697d6f639e851a97cc5be2..cf1950b6ec81844fb638006d35e208ba48a8c962 100644 (file)
@@ -1485,6 +1485,7 @@ do_connect(enum trivalue reuse_previous_specification,
        bool            keep_password;
        bool            has_connection_string;
        bool            reuse_previous;
+       PQExpBufferData connstr;
 
        has_connection_string = dbname ?
                recognized_connection_string(dbname) : false;
@@ -1536,7 +1537,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
@@ -1641,8 +1650,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
index 0c059665274331408e4e61a5aaa18b9c169ee0e1..0fd21200eb515f5192c499104f6cdc62f77345a8 100644 (file)
@@ -32,7 +32,7 @@ dropdb: dropdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq
 droplang: droplang.o common.o print.o mbprint.o | submake-libpq
 dropuser: dropuser.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq
 clusterdb: clusterdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq
-vacuumdb: vacuumdb.o common.o | submake-libpq
+vacuumdb: vacuumdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq
 reindexdb: reindexdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq
 
 dumputils.c keywords.c: % : $(top_srcdir)/src/bin/pg_dump/%
index 8ed839490c83a1af035d6b1e15a46c0f21ecf65e..7088a6cae93479dba60d003c6388781c3a11b8e4 100644 (file)
@@ -204,12 +204,14 @@ cluster_all_databases(bool verbose, const char *host, const char *port,
 {
        PGconn     *conn;
        PGresult   *result;
+       PQExpBufferData connstr;
        int                     i;
 
        conn = connectDatabase("postgres", host, port, username, prompt_password, progname);
        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);
@@ -220,10 +222,15 @@ cluster_all_databases(bool verbose, const char *host, const char *port,
                        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);
 }
index 6d7039f8575755bab98d7f4b6081547416245eec..4bcec082b990098219d184d0aa03b2efaa02eb43 100644 (file)
@@ -258,12 +258,14 @@ reindex_all_databases(const char *host, const char *port,
 {
        PGconn     *conn;
        PGresult   *result;
+       PQExpBufferData connstr;
        int                     i;
 
        conn = connectDatabase("postgres", host, port, username, prompt_password, progname);
        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);
@@ -274,9 +276,15 @@ reindex_all_databases(const char *host, const char *port,
                        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);
 }
@@ -293,7 +301,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))
        {
index 84a9dcd38e5126617205606420421083e1f5c9de..5dbc01ec0ac5423ee34b2aa27f82231dc67a8eff 100644 (file)
@@ -12,6 +12,7 @@
 
 #include "postgres_fe.h"
 #include "common.h"
+#include "dumputils.h"
 
 
 static void vacuum_one_database(const char *dbname, bool full, bool verbose,
@@ -296,12 +297,14 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool analyze_onl
 {
        PGconn     *conn;
        PGresult   *result;
+       PQExpBufferData connstr;
        int                     i;
 
        conn = connectDatabase("postgres", host, port, username, prompt_password, progname);
        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);
@@ -312,10 +315,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);
 }
index 43911a2dea34687353e0d0278f222df65f850d84..bac693273fb301839437794979981ef4f436e18a 100644 (file)
@@ -4377,10 +4377,15 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
  * Defaults are supplied (from a service file, environment variables, etc)
  * for unspecified options, but only if use_defaults is TRUE.
  *
- * If expand_dbname is non-zero, and the value passed for keyword "dbname"
- * contains an "=", assume it is a conninfo string and process it,
- * overriding any previously processed conflicting keywords. Subsequent
- * keywords will take precedence, however.
+ * If expand_dbname is non-zero, and the value passed for the first occurrence
+ * of "dbname" keyword contains an "=", assume it is a conninfo string and
+ * process it, overriding any previously processed conflicting
+ * keywords. Subsequent keywords will take 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 **keywords, const char **values,