mirror of
https://github.com/postgres/postgres.git
synced 2025-05-02 11:44:50 +03:00
Empty search_path in Autovacuum and non-psql/pgbench clients.
This makes the client programs behave as documented regardless of the connect-time search_path and regardless of user-created objects. Today, a malicious user with CREATE permission on a search_path schema can take control of certain of these clients' queries and invoke arbitrary SQL functions under the client identity, often a superuser. This is exploitable in the default configuration, where all users have CREATE privilege on schema "public". This changes behavior of user-defined code stored in the database, like pg_index.indexprs and pg_extension_config_dump(). If they reach code bearing unqualified names, "does not exist" or "no schema has been selected to create in" errors might appear. Users may fix such errors by schema-qualifying affected names. After upgrading, consider watching server logs for these errors. The --table arguments of src/bin/scripts clients have been lax; for example, "vacuumdb -Zt pg_am\;CHECKPOINT" performed a checkpoint. That now fails, but for now, "vacuumdb -Zt 'pg_am(amname);CHECKPOINT'" still performs a checkpoint. Back-patch to 9.3 (all supported versions). Reviewed by Tom Lane, though this fix strategy was not his first choice. Reported by Arseniy Sharoglazov. Security: CVE-2018-1058
This commit is contained in:
parent
de8ffd6663
commit
3db38b0cef
@ -16,6 +16,7 @@
|
||||
|
||||
extern char *optarg;
|
||||
|
||||
#include "fe_utils/connect.h"
|
||||
#include "libpq-fe.h"
|
||||
|
||||
/* an extensible array to keep track of elements to show */
|
||||
@ -269,6 +270,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
|
||||
@ -328,6 +330,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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <termios.h>
|
||||
#endif
|
||||
|
||||
#include "fe_utils/connect.h"
|
||||
#include "libpq-fe.h"
|
||||
|
||||
#define atooid(x) ((Oid) strtoul((x), NULL, 10))
|
||||
@ -138,11 +139,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));
|
||||
|
@ -557,6 +557,12 @@ AutoVacLauncherMain(int argc, char *argv[])
|
||||
/* must unblock signals before calling rebuild_database_list */
|
||||
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
|
||||
@ -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
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
||||
extern char *optarg;
|
||||
@ -969,6 +970,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.
|
||||
*/
|
||||
@ -1235,13 +1239,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)
|
||||
appendPQExpBuffer(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,
|
||||
@ -1249,7 +1260,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++)
|
||||
{
|
||||
|
@ -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"
|
||||
@ -1958,12 +1959,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;
|
||||
}
|
||||
|
@ -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) $< .
|
||||
@ -66,5 +67,5 @@ 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
|
||||
|
@ -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);
|
||||
|
||||
appendPQExpBuffer(&sql, "CLUSTER");
|
||||
if (verbose)
|
||||
appendPQExpBuffer(&sql, " VERBOSE");
|
||||
if (table)
|
||||
appendPQExpBuffer(&sql, " %s", table);
|
||||
{
|
||||
appendPQExpBufferChar(&sql, ' ');
|
||||
appendQualifiedRelation(&sql, table, conn, progname, echo);
|
||||
}
|
||||
appendPQExpBuffer(&sql, ";\n");
|
||||
|
||||
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);
|
||||
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "dumputils.h"
|
||||
#include "fe_utils/connect.h"
|
||||
|
||||
static void SetCancelConn(PGconn *conn);
|
||||
static void ResetCancelConn(void);
|
||||
@ -90,9 +92,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;
|
||||
@ -166,6 +169,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;
|
||||
}
|
||||
|
||||
@ -173,24 +180,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;
|
||||
}
|
||||
@ -276,6 +283,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.
|
||||
*/
|
||||
|
@ -31,11 +31,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);
|
||||
@ -46,6 +47,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);
|
||||
|
@ -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", sql.data);
|
||||
|
@ -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\" "
|
||||
@ -179,7 +179,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
|
||||
|
@ -246,7 +246,7 @@ main(int argc, char *argv[])
|
||||
login = TRI_YES;
|
||||
|
||||
conn = connectDatabase("postgres", host, port, username, prompt_password,
|
||||
progname, false);
|
||||
progname, echo, false);
|
||||
|
||||
initPQExpBuffer(&sql);
|
||||
|
||||
|
@ -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", sql.data);
|
||||
|
@ -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\" "
|
||||
@ -180,13 +180,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
|
||||
|
@ -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", sql.data);
|
||||
|
@ -245,17 +245,18 @@ 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);
|
||||
|
||||
appendPQExpBuffer(&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, fmtId(PQdb(conn)));
|
||||
appendPQExpBuffer(&sql, ";\n");
|
||||
|
||||
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);
|
||||
|
||||
|
@ -244,7 +244,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)
|
||||
{
|
||||
@ -297,7 +297,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);
|
||||
}
|
||||
appendPQExpBuffer(&sql, ";\n");
|
||||
|
||||
if (!executeMaintenanceCommand(conn, sql.data, echo))
|
||||
@ -329,7 +332,7 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool analyze_onl
|
||||
int i;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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 \
|
||||
|
28
src/include/fe_utils/connect.h
Normal file
28
src/include/fe_utils/connect.h
Normal file
@ -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 */
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user