aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/oid2name/oid2name.c13
-rw-r--r--contrib/pg_upgrade/server.c3
-rw-r--r--contrib/vacuumlo/vacuumlo.c8
-rw-r--r--src/backend/postmaster/autovacuum.c14
-rw-r--r--src/bin/pg_basebackup/streamutil.c18
-rw-r--r--src/bin/pg_dump/dumputils.c16
-rw-r--r--src/bin/pg_dump/pg_backup_db.c11
-rw-r--r--src/bin/pg_dump/pg_dump.c17
-rw-r--r--src/bin/pg_dump/pg_dumpall.c7
-rw-r--r--src/bin/scripts/Makefile23
-rw-r--r--src/bin/scripts/clusterdb.c14
-rw-r--r--src/bin/scripts/common.c137
-rw-r--r--src/bin/scripts/common.h10
-rw-r--r--src/bin/scripts/createdb.c2
-rw-r--r--src/bin/scripts/createlang.c4
-rw-r--r--src/bin/scripts/createuser.c2
-rw-r--r--src/bin/scripts/dropdb.c3
-rw-r--r--src/bin/scripts/droplang.c10
-rw-r--r--src/bin/scripts/dropuser.c2
-rw-r--r--src/bin/scripts/reindexdb.c21
-rw-r--r--src/bin/scripts/t/010_clusterdb.pl2
-rw-r--r--src/bin/scripts/t/090_reindexdb.pl4
-rw-r--r--src/bin/scripts/t/100_vacuumdb.pl24
-rw-r--r--src/bin/scripts/vacuumdb.c9
-rw-r--r--src/include/Makefile3
-rw-r--r--src/include/fe_utils/connect.h28
-rw-r--r--src/tools/findoidjoins/findoidjoins.c9
27 files changed, 335 insertions, 79 deletions
diff --git a/contrib/oid2name/oid2name.c b/contrib/oid2name/oid2name.c
index e5eeec21c15..91da40352b4 100644
--- a/contrib/oid2name/oid2name.c
+++ b/contrib/oid2name/oid2name.c
@@ -9,6 +9,7 @@
*/
#include "postgres_fe.h"
+#include "fe_utils/connect.h"
#include "libpq-fe.h"
#include "pg_getopt.h"
@@ -263,6 +264,7 @@ sql_conn(struct options * my_opts)
PGconn *conn;
char *password = NULL;
bool new_pass;
+ PGresult *res;
/*
* Start the connection. Loop until we have a password if requested by
@@ -322,6 +324,17 @@ sql_conn(struct options * my_opts)
exit(1);
}
+ res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "oid2name: could not clear search_path: %s\n",
+ PQerrorMessage(conn));
+ PQclear(res);
+ PQfinish(conn);
+ exit(-1);
+ }
+ PQclear(res);
+
/* return the conn if good */
return conn;
}
diff --git a/contrib/pg_upgrade/server.c b/contrib/pg_upgrade/server.c
index e03e526987b..908efe66b8d 100644
--- a/contrib/pg_upgrade/server.c
+++ b/contrib/pg_upgrade/server.c
@@ -9,6 +9,7 @@
#include "postgres_fe.h"
+#include "fe_utils/connect.h"
#include "pg_upgrade.h"
@@ -39,6 +40,8 @@ connectToServer(ClusterInfo *cluster, const char *db_name)
exit(1);
}
+ PQclear(executeQueryOrDie(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
+
return conn;
}
diff --git a/contrib/vacuumlo/vacuumlo.c b/contrib/vacuumlo/vacuumlo.c
index c2e5bad438b..10daa85445c 100644
--- a/contrib/vacuumlo/vacuumlo.c
+++ b/contrib/vacuumlo/vacuumlo.c
@@ -21,6 +21,7 @@
#include <termios.h>
#endif
+#include "fe_utils/connect.h"
#include "libpq-fe.h"
#include "pg_getopt.h"
@@ -135,11 +136,8 @@ vacuumlo(const char *database, const struct _param * param)
fprintf(stdout, "Test run: no large objects will be removed!\n");
}
- /*
- * Don't get fooled by any non-system catalogs
- */
- res = PQexec(conn, "SET search_path = pg_catalog");
- if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr, "Failed to set search_path:\n");
fprintf(stderr, "%s", PQerrorMessage(conn));
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 48da5fa4565..f07f584f936 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -557,6 +557,12 @@ AutoVacLauncherMain(int argc, char *argv[])
PG_SETMASK(&UnBlockSig);
/*
+ * Set always-secure search path. Launcher doesn't connect to a database,
+ * so this has no effect.
+ */
+ SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
* Force zero_damaged_pages OFF in the autovac process, even if it is set
* in postgresql.conf. We don't really want such a dangerous option being
* applied non-interactively.
@@ -1599,6 +1605,14 @@ AutoVacWorkerMain(int argc, char *argv[])
PG_SETMASK(&UnBlockSig);
/*
+ * Set always-secure search path, so malicious users can't redirect user
+ * code (e.g. pg_index.indexprs). (That code runs in a
+ * SECURITY_RESTRICTED_OPERATION sandbox, so malicious users could not
+ * take control of the entire autovacuum worker in any case.)
+ */
+ SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
* Force zero_damaged_pages OFF in the autovac process, even if it is set
* in postgresql.conf. We don't really want such a dangerous option being
* applied non-interactively.
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 2ebd7e85262..22bf5f71c8b 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -29,6 +29,7 @@
#include "common/fe_memutils.h"
#include "datatype/timestamp.h"
+#include "fe_utils/connect.h"
const char *progname;
char *connection_string = NULL;
@@ -205,6 +206,23 @@ GetConnection(void)
if (conn_opts)
PQconninfoFree(conn_opts);
+ /* Set always-secure search path, so malicious users can't get control. */
+ if (dbname != NULL)
+ {
+ PGresult *res;
+
+ res = PQexec(tmpconn, ALWAYS_SECURE_SEARCH_PATH_SQL);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: could not clear search_path: %s\n"),
+ progname, PQerrorMessage(tmpconn));
+ PQclear(res);
+ PQfinish(tmpconn);
+ exit(1);
+ }
+ PQclear(res);
+ }
+
/*
* Ensure we have the same value of integer timestamps as the server we
* are connecting to.
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index fdf9d47b644..8eb9e0c9926 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -1376,8 +1376,9 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
/*
- * Now decide what we need to emit. Note there will be a leading "^(" in
- * the patterns in any case.
+ * Now decide what we need to emit. We may run under a hostile
+ * search_path, so qualify EVERY name. Note there will be a leading "^("
+ * in the patterns in any case.
*/
if (namebuf.len > 2)
{
@@ -1390,15 +1391,18 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
WHEREAND();
if (altnamevar)
{
- appendPQExpBuffer(buf, "(%s ~ ", namevar);
+ appendPQExpBuffer(buf,
+ "(%s OPERATOR(pg_catalog.~) ", namevar);
appendStringLiteralConn(buf, namebuf.data, conn);
- appendPQExpBuffer(buf, "\n OR %s ~ ", altnamevar);
+ appendPQExpBuffer(buf,
+ "\n OR %s OPERATOR(pg_catalog.~) ",
+ altnamevar);
appendStringLiteralConn(buf, namebuf.data, conn);
appendPQExpBufferStr(buf, ")\n");
}
else
{
- appendPQExpBuffer(buf, "%s ~ ", namevar);
+ appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
appendStringLiteralConn(buf, namebuf.data, conn);
appendPQExpBufferChar(buf, '\n');
}
@@ -1414,7 +1418,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
- appendPQExpBuffer(buf, "%s ~ ", schemavar);
+ appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
appendStringLiteralConn(buf, schemabuf.data, conn);
appendPQExpBufferChar(buf, '\n');
}
diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
index cbfdf4889e8..d21b5c483ae 100644
--- a/src/bin/pg_dump/pg_backup_db.c
+++ b/src/bin/pg_dump/pg_backup_db.c
@@ -10,6 +10,7 @@
*-------------------------------------------------------------------------
*/
+#include "fe_utils/connect.h"
#include "pg_backup_db.h"
#include "pg_backup_utils.h"
#include "dumputils.h"
@@ -96,6 +97,11 @@ ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
PQfinish(AH->connection);
AH->connection = newConn;
+ /* Start strict; later phases may override this. */
+ if (PQserverVersion(AH->connection) >= 70300)
+ PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
+ ALWAYS_SECURE_SEARCH_PATH_SQL));
+
return 1;
}
@@ -304,6 +310,11 @@ ConnectDatabase(Archive *AHX,
PQdb(AH->connection) ? PQdb(AH->connection) : "",
PQerrorMessage(AH->connection));
+ /* Start strict; later phases may override this. */
+ if (PQserverVersion(AH->connection) >= 70300)
+ PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH,
+ ALWAYS_SECURE_SEARCH_PATH_SQL));
+
/*
* We want to remember connection's actual password, whether or not we got
* it by prompting. So we don't just store the password variable.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 10607644889..4501c8bed8e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -61,6 +61,7 @@
#include "pg_backup_db.h"
#include "pg_backup_utils.h"
#include "dumputils.h"
+#include "fe_utils/connect.h"
#include "parallel.h"
@@ -979,6 +980,9 @@ setup_connection(Archive *AH, const char *dumpencoding, char *use_role)
PGconn *conn = GetConnection(AH);
const char *std_strings;
+ if (AH->remoteVersion >= 70300)
+ PQclear(ExecuteSqlQueryForSingleRow(AH, ALWAYS_SECURE_SEARCH_PATH_SQL));
+
/*
* Set the client encoding if requested.
*/
@@ -1245,13 +1249,20 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ /*
+ * Query must remain ABSOLUTELY devoid of unqualified names. This
+ * would be unnecessary given a pg_table_is_visible() variant taking a
+ * search_path argument.
+ */
if (cell != patterns->head)
appendPQExpBufferStr(query, "UNION ALL\n");
appendPQExpBuffer(query,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
- "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\n LEFT JOIN pg_catalog.pg_namespace n"
+ "\n ON n.oid OPERATOR(pg_catalog.=) c.relnamespace"
+ "\nWHERE c.relkind OPERATOR(pg_catalog.=) ANY"
+ "\n (array['%c', '%c', '%c', '%c', '%c'])\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
@@ -1259,7 +1270,9 @@ expand_table_name_patterns(Archive *fout,
"pg_catalog.pg_table_is_visible(c.oid)");
}
+ ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ PQclear(ExecuteSqlQueryForSingleRow(fout, ALWAYS_SECURE_SEARCH_PATH_SQL));
for (i = 0; i < PQntuples(res); i++)
{
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 99d10f0c059..fc2ee0a07e3 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -26,6 +26,7 @@
#include "dumputils.h"
#include "pg_backup.h"
+#include "fe_utils/connect.h"
/* version string we expect back from pg_dump */
#define PGDUMP_VERSIONSTR "pg_dump (PostgreSQL) " PG_VERSION "\n"
@@ -1970,12 +1971,8 @@ connectDatabase(const char *dbname, const char *connection_string,
exit_nicely(1);
}
- /*
- * On 7.3 and later, make sure we are not fooled by non-system schemas in
- * the search path.
- */
if (server_version >= 70300)
- executeCommand(conn, "SET search_path = pg_catalog");
+ PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
return conn;
}
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index eeb2539d372..a893920f9cc 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -25,16 +25,17 @@ all: $(PROGRAMS)
%: %.o $(WIN32RES)
$(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
-createdb: createdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-createlang: createlang.o common.o print.o mbprint.o | submake-libpq submake-libpgport
-createuser: createuser.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-dropdb: dropdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-droplang: droplang.o common.o print.o mbprint.o | submake-libpq submake-libpgport
-dropuser: dropuser.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-clusterdb: clusterdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-vacuumdb: vacuumdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-reindexdb: reindexdb.o common.o dumputils.o kwlookup.o keywords.o | submake-libpq submake-libpgport
-pg_isready: pg_isready.o common.o | submake-libpq submake-libpgport
+SCRIPTS_COMMON = common.o dumputils.o kwlookup.o keywords.o
+createdb: createdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+createlang: createlang.o $(SCRIPTS_COMMON) print.o mbprint.o | submake-libpq submake-libpgport
+createuser: createuser.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+dropdb: dropdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+droplang: droplang.o $(SCRIPTS_COMMON) print.o mbprint.o | submake-libpq submake-libpgport
+dropuser: dropuser.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+clusterdb: clusterdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+vacuumdb: vacuumdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+reindexdb: reindexdb.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
+pg_isready: pg_isready.o $(SCRIPTS_COMMON) | submake-libpq submake-libpgport
dumputils.c keywords.c: % : $(top_srcdir)/src/bin/pg_dump/%
rm -f $@ && $(LN_S) $< .
@@ -65,7 +66,7 @@ uninstall:
clean distclean maintainer-clean:
rm -f $(addsuffix $(X), $(PROGRAMS)) $(addsuffix .o, $(PROGRAMS))
- rm -f common.o dumputils.o kwlookup.o keywords.o print.o mbprint.o $(WIN32RES)
+ rm -f $(SCRIPTS_COMMON) print.o mbprint.o $(WIN32RES)
rm -f dumputils.c print.c mbprint.c kwlookup.c keywords.c
rm -rf tmp_check
diff --git a/src/bin/scripts/clusterdb.c b/src/bin/scripts/clusterdb.c
index c6d59138862..ffc99d43c1e 100644
--- a/src/bin/scripts/clusterdb.c
+++ b/src/bin/scripts/clusterdb.c
@@ -194,17 +194,21 @@ cluster_one_database(const char *dbname, bool verbose, const char *table,
PGconn *conn;
+ conn = connectDatabase(dbname, host, port, username, prompt_password,
+ progname, echo, false);
+
initPQExpBuffer(&sql);
appendPQExpBufferStr(&sql, "CLUSTER");
if (verbose)
appendPQExpBufferStr(&sql, " VERBOSE");
if (table)
- appendPQExpBuffer(&sql, " %s", table);
- appendPQExpBufferStr(&sql, ";");
+ {
+ appendPQExpBufferChar(&sql, ' ');
+ appendQualifiedRelation(&sql, table, conn, progname, echo);
+ }
+ appendPQExpBufferChar(&sql, ';');
- conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
if (!executeMaintenanceCommand(conn, sql.data, echo))
{
if (table)
@@ -233,7 +237,7 @@ cluster_all_databases(bool verbose, const char *maintenance_db,
int i;
conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
- prompt_password, progname);
+ prompt_password, progname, echo);
result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
PQfinish(conn);
diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c
index 311fed5090b..17bb6057f83 100644
--- a/src/bin/scripts/common.c
+++ b/src/bin/scripts/common.c
@@ -18,6 +18,8 @@
#include <unistd.h>
#include "common.h"
+#include "dumputils.h"
+#include "fe_utils/connect.h"
static void SetCancelConn(PGconn *conn);
static void ResetCancelConn(void);
@@ -57,9 +59,10 @@ handle_help_version_opts(int argc, char *argv[],
* interactive password prompt is automatically issued if required.
*/
PGconn *
-connectDatabase(const char *dbname, const char *pghost, const char *pgport,
- const char *pguser, enum trivalue prompt_password,
- const char *progname, bool fail_ok)
+connectDatabase(const char *dbname, const char *pghost,
+ const char *pgport, const char *pguser,
+ enum trivalue prompt_password, const char *progname,
+ bool echo, bool fail_ok)
{
PGconn *conn;
char *password = NULL;
@@ -133,6 +136,10 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
exit(1);
}
+ if (PQserverVersion(conn) >= 70300)
+ PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL,
+ progname, echo));
+
return conn;
}
@@ -140,24 +147,24 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
* Try to connect to the appropriate maintenance database.
*/
PGconn *
-connectMaintenanceDatabase(const char *maintenance_db, const char *pghost,
- const char *pgport, const char *pguser,
- enum trivalue prompt_password,
- const char *progname)
+connectMaintenanceDatabase(const char *maintenance_db,
+ const char *pghost, const char *pgport,
+ const char *pguser, enum trivalue prompt_password,
+ const char *progname, bool echo)
{
PGconn *conn;
/* If a maintenance database name was specified, just connect to it. */
if (maintenance_db)
return connectDatabase(maintenance_db, pghost, pgport, pguser,
- prompt_password, progname, false);
+ prompt_password, progname, echo, false);
/* Otherwise, try postgres first and then template1. */
conn = connectDatabase("postgres", pghost, pgport, pguser, prompt_password,
- progname, true);
+ progname, echo, true);
if (!conn)
conn = connectDatabase("template1", pghost, pgport, pguser,
- prompt_password, progname, false);
+ prompt_password, progname, echo, false);
return conn;
}
@@ -243,6 +250,116 @@ executeMaintenanceCommand(PGconn *conn, const char *query, bool echo)
return r;
}
+
+/*
+ * Split TABLE[(COLUMNS)] into TABLE and [(COLUMNS)] portions. When you
+ * finish using them, pg_free(*table). *columns is a pointer into "spec",
+ * possibly to its NUL terminator.
+ */
+static void
+split_table_columns_spec(const char *spec, int encoding,
+ char **table, const char **columns)
+{
+ bool inquotes = false;
+ const char *cp = spec;
+
+ /*
+ * Find the first '(' not identifier-quoted. Based on
+ * dequote_downcase_identifier().
+ */
+ while (*cp && (*cp != '(' || inquotes))
+ {
+ if (*cp == '"')
+ {
+ if (inquotes && cp[1] == '"')
+ cp++; /* pair does not affect quoting */
+ else
+ inquotes = !inquotes;
+ cp++;
+ }
+ else
+ cp += PQmblen(cp, encoding);
+ }
+ *table = pg_strdup(spec);
+ (*table)[cp - spec] = '\0'; /* no strndup */
+ *columns = cp;
+}
+
+/*
+ * Break apart TABLE[(COLUMNS)] of "spec". With the reset_val of search_path
+ * in effect, have regclassin() interpret the TABLE portion. Append to "buf"
+ * the qualified name of TABLE, followed by any (COLUMNS). Exit on failure.
+ * We use this to interpret --table=foo under the search path psql would get,
+ * in advance of "ANALYZE public.foo" under the always-secure search path.
+ */
+void
+appendQualifiedRelation(PQExpBuffer buf, const char *spec,
+ PGconn *conn, const char *progname, bool echo)
+{
+ char *table;
+ const char *columns;
+ PQExpBufferData sql;
+ PGresult *res;
+ int ntups;
+
+ /* Before 7.3, the concept of qualifying a name did not exist. */
+ if (PQserverVersion(conn) < 70300)
+ {
+ appendPQExpBufferStr(&sql, spec);
+ return;
+ }
+
+ split_table_columns_spec(spec, PQclientEncoding(conn), &table, &columns);
+
+ /*
+ * Query must remain ABSOLUTELY devoid of unqualified names. This would
+ * be unnecessary given a regclassin() variant taking a search_path
+ * argument.
+ */
+ initPQExpBuffer(&sql);
+ appendPQExpBufferStr(&sql,
+ "SELECT c.relname, ns.nspname\n"
+ " FROM pg_catalog.pg_class c,"
+ " pg_catalog.pg_namespace ns\n"
+ " WHERE c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
+ " AND c.oid OPERATOR(pg_catalog.=) ");
+ appendStringLiteralConn(&sql, table, conn);
+ appendPQExpBufferStr(&sql, "::pg_catalog.regclass;");
+
+ executeCommand(conn, "RESET search_path", progname, echo);
+
+ /*
+ * One row is a typical result, as is a nonexistent relation ERROR.
+ * regclassin() unconditionally accepts all-digits input as an OID; if no
+ * relation has that OID; this query returns no rows. Catalog corruption
+ * might elicit other row counts.
+ */
+ res = executeQuery(conn, sql.data, progname, echo);
+ ntups = PQntuples(res);
+ if (ntups != 1)
+ {
+ fprintf(stderr,
+ ngettext("%s: query returned %d row instead of one: %s\n",
+ "%s: query returned %d rows instead of one: %s\n",
+ ntups),
+ progname, ntups, sql.data);
+ PQfinish(conn);
+ exit(1);
+ }
+ appendPQExpBufferStr(buf,
+ fmtQualifiedId(PQserverVersion(conn),
+ PQgetvalue(res, 0, 1),
+ PQgetvalue(res, 0, 0)));
+ appendPQExpBufferStr(buf, columns);
+ PQclear(res);
+ termPQExpBuffer(&sql);
+ pg_free(table);
+
+ PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL,
+ progname, echo));
+}
+
+
/*
* Check yes/no answer in a localized way. 1=yes, 0=no, -1=neither.
*/
diff --git a/src/bin/scripts/common.h b/src/bin/scripts/common.h
index 691f6c6a02f..4067af34556 100644
--- a/src/bin/scripts/common.h
+++ b/src/bin/scripts/common.h
@@ -30,11 +30,12 @@ extern void handle_help_version_opts(int argc, char *argv[],
extern PGconn *connectDatabase(const char *dbname, const char *pghost,
const char *pgport, const char *pguser,
enum trivalue prompt_password, const char *progname,
- bool fail_ok);
+ bool echo, bool fail_ok);
extern PGconn *connectMaintenanceDatabase(const char *maintenance_db,
- const char *pghost, const char *pgport, const char *pguser,
- enum trivalue prompt_password, const char *progname);
+ const char *pghost, const char *pgport,
+ const char *pguser, enum trivalue prompt_password,
+ const char *progname, bool echo);
extern PGresult *executeQuery(PGconn *conn, const char *query,
const char *progname, bool echo);
@@ -45,6 +46,9 @@ extern void executeCommand(PGconn *conn, const char *query,
extern bool executeMaintenanceCommand(PGconn *conn, const char *query,
bool echo);
+extern void appendQualifiedRelation(PQExpBuffer buf, const char *name,
+ PGconn *conn, const char *progname, bool echo);
+
extern bool yesno_prompt(const char *question);
extern void setup_cancel_handler(void);
diff --git a/src/bin/scripts/createdb.c b/src/bin/scripts/createdb.c
index 64badd6bd30..c47ef3f0016 100644
--- a/src/bin/scripts/createdb.c
+++ b/src/bin/scripts/createdb.c
@@ -202,7 +202,7 @@ main(int argc, char *argv[])
maintenance_db = "template1";
conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
- prompt_password, progname);
+ prompt_password, progname, echo);
if (echo)
printf("%s\n", sql.data);
diff --git a/src/bin/scripts/createlang.c b/src/bin/scripts/createlang.c
index 9a7adfa72c6..7f16bcaf403 100644
--- a/src/bin/scripts/createlang.c
+++ b/src/bin/scripts/createlang.c
@@ -141,7 +141,7 @@ main(int argc, char *argv[])
static const bool translate_columns[] = {false, true};
conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
"(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
@@ -181,7 +181,7 @@ main(int argc, char *argv[])
*p += ('a' - 'A');
conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
/*
* Make sure the language isn't already installed
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index 2d49bc2f1e8..1b192af6780 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -251,7 +251,7 @@ main(int argc, char *argv[])
login = TRI_YES;
conn = connectDatabase("postgres", host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
initPQExpBuffer(&sql);
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index e750b449d6b..6d3db8ebae1 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -129,7 +129,8 @@ main(int argc, char *argv[])
maintenance_db = "template1";
conn = connectMaintenanceDatabase(maintenance_db,
- host, port, username, prompt_password, progname);
+ host, port, username, prompt_password,
+ progname, echo);
if (echo)
printf("%s\n", sql.data);
diff --git a/src/bin/scripts/droplang.c b/src/bin/scripts/droplang.c
index 60b56d5e533..84f115f891a 100644
--- a/src/bin/scripts/droplang.c
+++ b/src/bin/scripts/droplang.c
@@ -140,7 +140,7 @@ main(int argc, char *argv[])
static const bool translate_columns[] = {false, true};
conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
"(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
@@ -182,13 +182,7 @@ main(int argc, char *argv[])
*p += ('a' - 'A');
conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
-
- /*
- * Force schema search path to be just pg_catalog, so that we don't have
- * to be paranoid about search paths below.
- */
- executeCommand(conn, "SET search_path = pg_catalog;", progname, echo);
+ progname, echo, false);
/*
* Make sure the language is installed
diff --git a/src/bin/scripts/dropuser.c b/src/bin/scripts/dropuser.c
index 69a5a493ae3..b92559d94be 100644
--- a/src/bin/scripts/dropuser.c
+++ b/src/bin/scripts/dropuser.c
@@ -129,7 +129,7 @@ main(int argc, char *argv[])
(if_exists ? "IF EXISTS " : ""), fmtId(dropuser));
conn = connectDatabase("postgres", host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
if (echo)
printf("%s\n", sql.data);
diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c
index a868ed75855..c95c1fff152 100644
--- a/src/bin/scripts/reindexdb.c
+++ b/src/bin/scripts/reindexdb.c
@@ -245,18 +245,19 @@ reindex_one_database(const char *name, const char *dbname, const char *type,
PGconn *conn;
conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
initPQExpBuffer(&sql);
- appendPQExpBufferStr(&sql, "REINDEX");
- if (strcmp(type, "TABLE") == 0)
- appendPQExpBuffer(&sql, " TABLE %s", name);
- else if (strcmp(type, "INDEX") == 0)
- appendPQExpBuffer(&sql, " INDEX %s", name);
+ appendPQExpBufferStr(&sql, "REINDEX ");
+ appendPQExpBufferStr(&sql, type);
+ appendPQExpBufferChar(&sql, ' ');
+ if (strcmp(type, "TABLE") == 0 ||
+ strcmp(type, "INDEX") == 0)
+ appendQualifiedRelation(&sql, name, conn, progname, echo);
else if (strcmp(type, "DATABASE") == 0)
- appendPQExpBuffer(&sql, " DATABASE %s", fmtId(PQdb(conn)));
- appendPQExpBufferStr(&sql, ";");
+ appendPQExpBufferStr(&sql, fmtId(PQdb(conn)));
+ appendPQExpBufferChar(&sql, ';');
if (!executeMaintenanceCommand(conn, sql.data, echo))
{
@@ -289,7 +290,7 @@ reindex_all_databases(const char *maintenance_db,
int i;
conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
- prompt_password, progname);
+ prompt_password, progname, echo);
result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
PQfinish(conn);
@@ -326,7 +327,7 @@ reindex_system_catalogs(const char *dbname, const char *host, const char *port,
PQExpBufferData sql;
conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
initPQExpBuffer(&sql);
diff --git a/src/bin/scripts/t/010_clusterdb.pl b/src/bin/scripts/t/010_clusterdb.pl
index dc0d78a27d3..bdb32734510 100644
--- a/src/bin/scripts/t/010_clusterdb.pl
+++ b/src/bin/scripts/t/010_clusterdb.pl
@@ -22,5 +22,5 @@ psql 'postgres',
'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a); CLUSTER test1 USING test1x';
issues_sql_like(
[ 'clusterdb', '-t', 'test1', 'postgres' ],
- qr/statement: CLUSTER test1;/,
+ qr/statement: CLUSTER public\.test1;/,
'cluster specific table');
diff --git a/src/bin/scripts/t/090_reindexdb.pl b/src/bin/scripts/t/090_reindexdb.pl
index bba1667b56f..54864b2209e 100644
--- a/src/bin/scripts/t/090_reindexdb.pl
+++ b/src/bin/scripts/t/090_reindexdb.pl
@@ -21,11 +21,11 @@ psql 'postgres',
'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);';
issues_sql_like(
[ 'reindexdb', '-t', 'test1', 'postgres' ],
- qr/statement: REINDEX TABLE test1;/,
+ qr/statement: REINDEX TABLE public\.test1;/,
'reindex specific table');
issues_sql_like(
[ 'reindexdb', '-i', 'test1x', 'postgres' ],
- qr/statement: REINDEX INDEX test1x;/,
+ qr/statement: REINDEX INDEX public\.test1x;/,
'reindex specific index');
issues_sql_like(
[ 'reindexdb', '-s', 'postgres' ],
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index ac160ba8374..bdd03bc2449 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -1,7 +1,7 @@
use strict;
use warnings;
use TestLib;
-use Test::More tests => 18;
+use Test::More tests => 23;
program_help_ok('vacuumdb');
program_version_ok('vacuumdb');
@@ -30,3 +30,25 @@ issues_sql_like(
[ 'vacuumdb', '-Z', 'postgres' ],
qr/statement: ANALYZE;/,
'vacuumdb -Z');
+command_ok([qw(vacuumdb -Z --table=pg_am dbname=template1)],
+ 'vacuumdb with connection string');
+
+command_fails([qw(vacuumdb -Zt pg_am;ABORT postgres)],
+ 'trailing command in "-t", without COLUMNS');
+# Unwanted; better if it failed.
+command_ok([qw(vacuumdb -Zt pg_am(amname);ABORT postgres)],
+ 'trailing command in "-t", with COLUMNS');
+
+psql('postgres', q|
+ CREATE TABLE "need""q(uot" (")x" text);
+
+ CREATE FUNCTION f0(int) RETURNS int LANGUAGE SQL AS 'SELECT $1 * $1';
+ CREATE FUNCTION f1(int) RETURNS int LANGUAGE SQL AS 'SELECT f0($1)';
+ CREATE TABLE funcidx (x int);
+ INSERT INTO funcidx VALUES (0),(1),(2),(3);
+ CREATE INDEX i0 ON funcidx ((f1(x)));
+|);
+command_ok([qw|vacuumdb -Z --table="need""q(uot"(")x") postgres|],
+ 'column list');
+command_fails([qw|vacuumdb -Zt funcidx postgres|],
+ 'unqualifed name via functional index');
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index eac5bfe5671..25567777070 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -266,7 +266,7 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz
initPQExpBuffer(&sql);
conn = connectDatabase(dbname, host, port, username, prompt_password,
- progname, false);
+ progname, echo, false);
if (analyze_only)
{
@@ -319,7 +319,10 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz
}
}
if (table)
- appendPQExpBuffer(&sql, " %s", table);
+ {
+ appendPQExpBufferChar(&sql, ' ');
+ appendQualifiedRelation(&sql, table, conn, progname, echo);
+ }
appendPQExpBufferStr(&sql, ";");
if (analyze_in_stages)
@@ -386,7 +389,7 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool analyze_onl
int stage;
conn = connectMaintenanceDatabase(maintenance_db, host, port,
- username, prompt_password, progname);
+ username, prompt_password, progname, echo);
result = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", progname, echo);
PQfinish(conn);
diff --git a/src/include/Makefile b/src/include/Makefile
index 578a7784616..9bba3082a98 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -17,7 +17,8 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
# Subdirectories containing headers for server-side dev
-SUBDIRS = access bootstrap catalog commands common datatype executor foreign \
+SUBDIRS = access bootstrap catalog commands common datatype \
+ executor fe_utils foreign \
lib libpq mb nodes optimizer parser postmaster regex replication \
rewrite storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/win32 port/win32_msvc \
diff --git a/src/include/fe_utils/connect.h b/src/include/fe_utils/connect.h
new file mode 100644
index 00000000000..fa293d2458d
--- /dev/null
+++ b/src/include/fe_utils/connect.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * Interfaces in support of FE/BE connections.
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/fe_utils/connect.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CONNECT_H
+#define CONNECT_H
+
+/*
+ * This SQL statement installs an always-secure search path, so malicious
+ * users can't take control. CREATE of an unqualified name will fail, because
+ * this selects no creation schema. This does not demote pg_temp, so it is
+ * suitable where we control the entire FE/BE connection but not suitable in
+ * SECURITY DEFINER functions. This is portable to PostgreSQL 7.3, which
+ * introduced schemas. When connected to an older version from code that
+ * might work with the old server, skip this.
+ */
+#define ALWAYS_SECURE_SEARCH_PATH_SQL \
+ "SELECT pg_catalog.set_config('search_path', '', false)"
+
+#endif /* CONNECT_H */
diff --git a/src/tools/findoidjoins/findoidjoins.c b/src/tools/findoidjoins/findoidjoins.c
index 989e992edb4..512d19ad4b9 100644
--- a/src/tools/findoidjoins/findoidjoins.c
+++ b/src/tools/findoidjoins/findoidjoins.c
@@ -7,6 +7,7 @@
*/
#include "postgres_fe.h"
+#include "fe_utils/connect.h"
#include "libpq-fe.h"
#include "pqexpbuffer.h"
@@ -44,6 +45,14 @@ main(int argc, char **argv)
exit(EXIT_FAILURE);
}
+ res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
+ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, "sql error: %s\n", PQerrorMessage(conn));
+ exit(EXIT_FAILURE);
+ }
+ PQclear(res);
+
/* Get a list of relations that have OIDs */
printfPQExpBuffer(&sql, "%s",