diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index ec8ea8fd985..dc1342260b6 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -92,6 +92,18 @@ PostgreSQL documentation
light of the limitations listed below.
+
+
+ Restoring a dump causes the destination to execute arbitrary code of the
+ source superusers' choice. Partial dumps and partial restores do not limit
+ that. If the source superusers are not trusted, the dumped SQL statements
+ must be inspected before restoring. Non-plain-text dumps can be inspected
+ by using pg_restore's
+ option. Note that the client running the dump and restore need not trust
+ the source or destination superusers.
+
+
+
@@ -1078,6 +1090,29 @@ PostgreSQL documentation
+
+
+
+
+ Use the provided string as the psql
+ \restrict key in the dump output. This can only be
+ specified for plain-text dumps, i.e., when is
+ set to plain or the option
+ is omitted. If no restrict key is specified,
+ pg_dump will generate a random one as
+ needed. Keys may contain only alphanumeric characters.
+
+
+ This option is primarily intended for testing purposes and other
+ scenarios that require repeatable output (e.g., comparing dump files).
+ It is not recommended for general use, as a malicious server with
+ advance knowledge of the key may be able to inject arbitrary code that
+ will be executed on the machine that runs
+ psql with the dump output.
+
+
+
+
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index ba005ff19e2..1bddc8883f0 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -66,6 +66,16 @@ PostgreSQL documentation
linkend="libpq-pgpass"/> for more information.
+
+
+ Restoring a dump causes the destination to execute arbitrary code of the
+ source superusers' choice. Partial dumps and partial restores do not limit
+ that. If the source superusers are not trusted, the dumped SQL statements
+ must be inspected before restoring. Note that the client running the dump
+ and restore need not trust the source or destination superusers.
+
+
+
@@ -524,6 +534,26 @@ PostgreSQL documentation
+
+
+
+
+ Use the provided string as the psql
+ \restrict key in the dump output. If no restrict
+ key is specified, pg_dumpall will generate a
+ random one as needed. Keys may contain only alphanumeric characters.
+
+
+ This option is primarily intended for testing purposes and other
+ scenarios that require repeatable output (e.g., comparing dump files).
+ It is not recommended for general use, as a malicious server with
+ advance knowledge of the key may be able to inject arbitrary code that
+ will be executed on the machine that runs
+ psql with the dump output.
+
+
+
+
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index a81583191c1..d2ff765dc74 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -68,6 +68,18 @@ PostgreSQL documentation
pg_restore will not be able to load the data
using COPY statements.
+
+
+
+ Restoring a dump causes the destination to execute arbitrary code of the
+ source superusers' choice. Partial dumps and partial restores do not limit
+ that. If the source superusers are not trusted, the dumped SQL statements
+ must be inspected before restoring. Non-plain-text dumps can be inspected
+ by using pg_restore's
+ option. Note that the client running the dump and restore need not trust
+ the source or destination superusers.
+
+
@@ -675,6 +687,28 @@ PostgreSQL documentation
+
+
+
+
+ Use the provided string as the psql
+ \restrict key in the dump output. This can only be
+ specified for SQL script output, i.e., when the
+ option is used. If no restrict key is specified,
+ pg_restore will generate a random one as
+ needed. Keys may contain only alphanumeric characters.
+
+
+ This option is primarily intended for testing purposes and other
+ scenarios that require repeatable output (e.g., comparing dump files).
+ It is not recommended for general use, as a malicious server with
+ advance knowledge of the key may be able to inject arbitrary code that
+ will be executed on the machine that runs
+ psql with the dump output.
+
+
+
+
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 494aa0b5bbc..0459fc3777e 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -70,6 +70,14 @@ PostgreSQL documentation
pg_upgrade supports upgrades from 9.2.X and later to the current
major release of PostgreSQL, including snapshot and beta releases.
+
+
+
+ Upgrading a cluster causes the destination to execute arbitrary code of the
+ source superusers' choice. Ensure that the source superusers are trusted
+ before upgrading.
+
+
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 7ff6e1d95d7..747408932a7 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3340,6 +3340,24 @@ lo_import 152801
+
+ \restrict restrict_key
+
+
+ Enter "restricted" mode with the provided key. In this mode, the only
+ allowed meta-command is \unrestrict, to exit
+ restricted mode. The key may contain only alphanumeric characters.
+
+
+ This command is primarily intended for use in plain-text dumps
+ generated by pg_dump,
+ pg_dumpall, and
+ pg_restore, but it may be useful elsewhere.
+
+
+
+
+
\s [ filename ]
@@ -3514,6 +3532,24 @@ testdb=> \setenv LESS -imx4F
+
+ \unrestrict restrict_key
+
+
+ Exit "restricted" mode (i.e., where all other meta-commands are
+ blocked), provided the specified key matches the one given to
+ \restrict when restricted mode was entered.
+
+
+ This command is primarily intended for use in plain-text dumps
+ generated by pg_dump,
+ pg_dumpall, and
+ pg_restore, but it may be useful elsewhere.
+
+
+
+
+
\unset name
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 43891955761..44ff2b17f1d 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -19,6 +19,7 @@
#include "dumputils.h"
#include "fe_utils/string_utils.h"
+static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static bool parseAclItem(const char *item, const char *type,
const char *name, const char *subname, int remoteVersion,
@@ -919,3 +920,40 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
pg_free(mine);
}
+
+/*
+ * Generates a valid restrict key (i.e., an alphanumeric string) for use with
+ * psql's \restrict and \unrestrict meta-commands. For safety, the value is
+ * chosen at random.
+ */
+char *
+generate_restrict_key(void)
+{
+ uint8 buf[64];
+ char *ret = palloc(sizeof(buf));
+
+ if (!pg_strong_random(buf, sizeof(buf)))
+ return NULL;
+
+ for (int i = 0; i < sizeof(buf) - 1; i++)
+ {
+ uint8 idx = buf[i] % strlen(restrict_chars);
+
+ ret[i] = restrict_chars[idx];
+ }
+ ret[sizeof(buf) - 1] = '\0';
+
+ return ret;
+}
+
+/*
+ * Checks that a given restrict key (intended for use with psql's \restrict and
+ * \unrestrict meta-commands) contains only alphanumeric characters.
+ */
+bool
+valid_restrict_key(const char *restrict_key)
+{
+ return restrict_key != NULL &&
+ restrict_key[0] != '\0' &&
+ strspn(restrict_key, restrict_chars) == strlen(restrict_key);
+}
diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h
index a6b8b478638..8f8964c390e 100644
--- a/src/bin/pg_dump/dumputils.h
+++ b/src/bin/pg_dump/dumputils.h
@@ -64,4 +64,7 @@ extern void makeAlterConfigCommand(PGconn *conn, const char *configitem,
const char *type2, const char *name2,
PQExpBuffer buf);
+extern char *generate_restrict_key(void);
+extern bool valid_restrict_key(const char *restrict_key);
+
#endif /* DUMPUTILS_H */
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index aba780ef4b1..558a8f00abf 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -154,6 +154,8 @@ typedef struct _restoreOptions
int enable_row_security;
int sequence_data; /* dump sequence data even in schema-only mode */
int binary_upgrade;
+
+ char *restrict_key;
} RestoreOptions;
typedef struct _dumpOptions
@@ -200,6 +202,8 @@ typedef struct _dumpOptions
int sequence_data; /* dump sequence data even in schema-only mode */
int do_nothing;
+
+ char *restrict_key;
} DumpOptions;
/*
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index b1ea5db46ee..1c1675fa2f8 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -204,6 +204,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
dopt->include_everything = ropt->include_everything;
dopt->enable_row_security = ropt->enable_row_security;
dopt->sequence_data = ropt->sequence_data;
+ dopt->restrict_key = ropt->restrict_key ? pg_strdup(ropt->restrict_key) : NULL;
return dopt;
}
@@ -466,6 +467,17 @@ RestoreArchive(Archive *AHX)
ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
+ /*
+ * If generating plain-text output, enter restricted mode to block any
+ * unexpected psql meta-commands. A malicious source might try to inject
+ * a variety of things via bogus responses to queries. While we cannot
+ * prevent such sources from affecting the destination at restore time, we
+ * can block psql meta-commands so that the client machine that runs psql
+ * with the dump output remains unaffected.
+ */
+ if (ropt->restrict_key)
+ ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
+
if (AH->archiveRemoteVersion)
ahprintf(AH, "-- Dumped from database version %s\n",
AH->archiveRemoteVersion);
@@ -741,6 +753,14 @@ RestoreArchive(Archive *AHX)
ahprintf(AH, "--\n-- PostgreSQL database dump complete\n--\n\n");
+ /*
+ * If generating plain-text output, exit restricted mode at the very end
+ * of the script. This is not pro forma; in particular, pg_dumpall
+ * requires this when transitioning from one database to another.
+ */
+ if (ropt->restrict_key)
+ ahprintf(AH, "\\unrestrict %s\n\n", ropt->restrict_key);
+
/*
* Clean up & we're done.
*/
@@ -3228,11 +3248,21 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname)
else
{
PQExpBufferData connectbuf;
+ RestoreOptions *ropt = AH->public.ropt;
+
+ /*
+ * We must temporarily exit restricted mode for \connect, etc.
+ * Anything added between this line and the following \restrict must
+ * be careful to avoid any possible meta-command injection vectors.
+ */
+ ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key);
initPQExpBuffer(&connectbuf);
appendPsqlMetaConnect(&connectbuf, dbname);
- ahprintf(AH, "%s\n", connectbuf.data);
+ ahprintf(AH, "%s", connectbuf.data);
termPQExpBuffer(&connectbuf);
+
+ ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
}
/*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 427e4ae1322..829800ec812 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -432,6 +432,7 @@ main(int argc, char **argv)
{"table-and-children", required_argument, NULL, 12},
{"exclude-table-and-children", required_argument, NULL, 13},
{"exclude-table-data-and-children", required_argument, NULL, 14},
+ {"restrict-key", required_argument, NULL, 25},
{NULL, 0, NULL, 0}
};
@@ -658,6 +659,10 @@ main(int argc, char **argv)
optarg);
break;
+ case 25:
+ dopt.restrict_key = pg_strdup(optarg);
+ break;
+
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -720,8 +725,22 @@ main(int argc, char **argv)
/* archiveFormat specific setup */
if (archiveFormat == archNull)
+ {
plainText = 1;
+ /*
+ * If you don't provide a restrict key, one will be appointed for you.
+ */
+ if (!dopt.restrict_key)
+ dopt.restrict_key = generate_restrict_key();
+ if (!dopt.restrict_key)
+ pg_fatal("could not generate restrict key");
+ if (!valid_restrict_key(dopt.restrict_key))
+ pg_fatal("invalid restrict key");
+ }
+ else if (dopt.restrict_key)
+ pg_fatal("option --restrict-key can only be used with --format=plain");
+
/*
* Custom and directory formats are compressed by default with gzip when
* available, not the others. If gzip is not available, no compression is
@@ -1017,6 +1036,7 @@ main(int argc, char **argv)
ropt->enable_row_security = dopt.enable_row_security;
ropt->sequence_data = dopt.sequence_data;
ropt->binary_upgrade = dopt.binary_upgrade;
+ ropt->restrict_key = dopt.restrict_key ? pg_strdup(dopt.restrict_key) : NULL;
ropt->compression_spec = compression_spec;
@@ -1120,6 +1140,7 @@ help(const char *progname)
printf(_(" --no-unlogged-table-data do not dump unlogged table data\n"));
printf(_(" --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n"));
printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n"));
+ printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n"));
printf(_(" --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n"));
printf(_(" --section=SECTION dump named section (pre-data, data, or post-data)\n"));
printf(_(" --serializable-deferrable wait until the dump can run without anomalies\n"));
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 41dc2cbaaca..b534df14b9e 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -121,6 +121,8 @@ static char *filename = NULL;
static SimpleStringList database_exclude_patterns = {NULL, NULL};
static SimpleStringList database_exclude_names = {NULL, NULL};
+static char *restrict_key;
+
#define exit_nicely(code) exit(code)
int
@@ -177,6 +179,7 @@ main(int argc, char *argv[])
{"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
{"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1},
{"rows-per-insert", required_argument, NULL, 7},
+ {"restrict-key", required_argument, NULL, 9},
{NULL, 0, NULL, 0}
};
@@ -360,6 +363,12 @@ main(int argc, char *argv[])
appendShellString(pgdumpopts, optarg);
break;
+ case 9:
+ restrict_key = pg_strdup(optarg);
+ appendPQExpBufferStr(pgdumpopts, " --restrict-key ");
+ appendShellString(pgdumpopts, optarg);
+ break;
+
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -455,6 +464,16 @@ main(int argc, char *argv[])
if (on_conflict_do_nothing)
appendPQExpBufferStr(pgdumpopts, " --on-conflict-do-nothing");
+ /*
+ * If you don't provide a restrict key, one will be appointed for you.
+ */
+ if (!restrict_key)
+ restrict_key = generate_restrict_key();
+ if (!restrict_key)
+ pg_fatal("could not generate restrict key");
+ if (!valid_restrict_key(restrict_key))
+ pg_fatal("invalid restrict key");
+
/*
* If there was a database specified on the command line, use that,
* otherwise try to connect to database "postgres", and failing that
@@ -542,6 +561,16 @@ main(int argc, char *argv[])
if (verbose)
dumpTimestamp("Started on");
+ /*
+ * Enter restricted mode to block any unexpected psql meta-commands. A
+ * malicious source might try to inject a variety of things via bogus
+ * responses to queries. While we cannot prevent such sources from
+ * affecting the destination at restore time, we can block psql
+ * meta-commands so that the client machine that runs psql with the dump
+ * output remains unaffected.
+ */
+ fprintf(OPF, "\\restrict %s\n\n", restrict_key);
+
/*
* We used to emit \connect postgres here, but that served no purpose
* other than to break things for installations without a postgres
@@ -602,6 +631,12 @@ main(int argc, char *argv[])
dumpTablespaces(conn);
}
+ /*
+ * Exit restricted mode just before dumping the databases. pg_dump will
+ * handle entering restricted mode again as appropriate.
+ */
+ fprintf(OPF, "\\unrestrict %s\n\n", restrict_key);
+
if (!globals_only && !roles_only && !tablespaces_only)
dumpDatabases(conn);
@@ -669,6 +704,7 @@ help(void)
printf(_(" --no-unlogged-table-data do not dump unlogged table data\n"));
printf(_(" --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n"));
printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n"));
+ printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n"));
printf(_(" --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n"));
printf(_(" --use-set-session-authorization\n"
" use SET SESSION AUTHORIZATION commands instead of\n"
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 049a1006347..f05c24510ac 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -123,6 +123,7 @@ main(int argc, char **argv)
{"no-publications", no_argument, &no_publications, 1},
{"no-security-labels", no_argument, &no_security_labels, 1},
{"no-subscriptions", no_argument, &no_subscriptions, 1},
+ {"restrict-key", required_argument, NULL, 6},
{NULL, 0, NULL, 0}
};
@@ -286,6 +287,10 @@ main(int argc, char **argv)
set_dump_section(optarg, &(opts->dumpSections));
break;
+ case 6:
+ opts->restrict_key = pg_strdup(optarg);
+ break;
+
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -321,8 +326,24 @@ main(int argc, char **argv)
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
exit_nicely(1);
}
+
+ if (opts->restrict_key)
+ pg_fatal("options -d/--dbname and --restrict-key cannot be used together");
+
opts->useDB = 1;
}
+ else
+ {
+ /*
+ * If you don't provide a restrict key, one will be appointed for you.
+ */
+ if (!opts->restrict_key)
+ opts->restrict_key = generate_restrict_key();
+ if (!opts->restrict_key)
+ pg_fatal("could not generate restrict key");
+ if (!valid_restrict_key(opts->restrict_key))
+ pg_fatal("invalid restrict key");
+ }
if (opts->dataOnly && opts->schemaOnly)
pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
@@ -472,6 +493,7 @@ usage(const char *progname)
printf(_(" --no-subscriptions do not restore subscriptions\n"));
printf(_(" --no-table-access-method do not restore table access methods\n"));
printf(_(" --no-tablespaces do not restore tablespace assignments\n"));
+ printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n"));
printf(_(" --section=SECTION restore named section (pre-data, data, or post-data)\n"));
printf(_(" --strict-names require table and/or schema include patterns to\n"
" match at least one entity each\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index b052510537d..b491e461158 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -729,6 +729,16 @@ my %full_runs = (
# This is where the actual tests are defined.
my %tests = (
+ 'restrict' => {
+ all_runs => 1,
+ regexp => qr/^\\restrict [a-zA-Z0-9]+$/m,
+ },
+
+ 'unrestrict' => {
+ all_runs => 1,
+ regexp => qr/^\\unrestrict [a-zA-Z0-9]+$/m,
+ },
+
'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT' => {
create_order => 14,
create_sql => 'ALTER DEFAULT PRIVILEGES
@@ -3874,7 +3884,6 @@ my %tests = (
},
'ALTER TABLE measurement PRIMARY KEY' => {
- all_runs => 1,
catch_all => 'CREATE ... commands',
create_order => 93,
create_sql =>
@@ -3927,7 +3936,6 @@ my %tests = (
},
'ALTER INDEX ... ATTACH PARTITION (primary key)' => {
- all_runs => 1,
catch_all => 'CREATE ... commands',
regexp => qr/^
\QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E
@@ -4959,9 +4967,10 @@ foreach my $run (sort keys %pgdump_runs)
next;
}
- # Run the test listed as a like, unless it is specifically noted
- # as an unlike (generally due to an explicit exclusion or similar).
- if ($tests{$test}->{like}->{$test_key}
+ # Run the test if all_runs is set or if listed as a like, unless it is
+ # specifically noted as an unlike (generally due to an explicit
+ # exclusion or similar).
+ if (($tests{$test}->{like}->{$test_key} || $tests{$test}->{all_runs})
&& !defined($tests{$test}->{unlike}->{$test_key}))
{
if (!ok($output_file =~ $tests{$test}->{regexp},
diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
index 3bf4e87b178..056538af53c 100644
--- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl
+++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl
@@ -264,6 +264,7 @@ if (defined($ENV{oldinstall}))
# that we need to use pg_dumpall from the new node here.
my @dump_command = (
'pg_dumpall', '--no-sync', '-d', $oldnode->connstr('postgres'),
+ '--restrict-key=test',
'-f', $dump1_file);
# --extra-float-digits is needed when upgrading from a version older than 11.
push(@dump_command, '--extra-float-digits', '0')
@@ -449,6 +450,7 @@ is( $result,
# Second dump from the upgraded instance.
@dump_command = (
'pg_dumpall', '--no-sync', '-d', $newnode->connstr('postgres'),
+ '--restrict-key=test',
'-f', $dump2_file);
# --extra-float-digits is needed when upgrading from a version older than 11.
push(@dump_command, '--extra-float-digits', '0')
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 0cf32d156a8..faad3790907 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -123,6 +123,8 @@ static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_b
static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf);
+static backslashResult exec_command_restrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd);
static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
@@ -132,6 +134,8 @@ static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_
static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd);
static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
@@ -181,6 +185,8 @@ static char *pset_value_string(const char *param, printQueryOpt *popt);
static void checkWin32Codepage(void);
#endif
+static bool restricted;
+static char *restrict_key;
/*----------
@@ -226,8 +232,19 @@ HandleSlashCmds(PsqlScanState scan_state,
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
- /* And try to execute it */
- status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
+ /*
+ * And try to execute it.
+ *
+ * If we are in "restricted" mode, the only allowable backslash command is
+ * \unrestrict (to exit restricted mode).
+ */
+ if (restricted && strcmp(cmd, "unrestrict") != 0)
+ {
+ pg_log_error("backslash commands are restricted; only \\unrestrict is allowed");
+ status = PSQL_CMD_ERROR;
+ }
+ else
+ status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
if (status == PSQL_CMD_UNKNOWN)
{
@@ -388,6 +405,8 @@ exec_command(const char *cmd,
status = exec_command_quit(scan_state, active_branch);
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
status = exec_command_reset(scan_state, active_branch, query_buf);
+ else if (strcmp(cmd, "restrict") == 0)
+ status = exec_command_restrict(scan_state, active_branch, cmd);
else if (strcmp(cmd, "s") == 0)
status = exec_command_s(scan_state, active_branch);
else if (strcmp(cmd, "set") == 0)
@@ -404,6 +423,8 @@ exec_command(const char *cmd,
status = exec_command_T(scan_state, active_branch);
else if (strcmp(cmd, "timing") == 0)
status = exec_command_timing(scan_state, active_branch);
+ else if (strcmp(cmd, "unrestrict") == 0)
+ status = exec_command_unrestrict(scan_state, active_branch, cmd);
else if (strcmp(cmd, "unset") == 0)
status = exec_command_unset(scan_state, active_branch, cmd);
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
@@ -2344,6 +2365,35 @@ exec_command_reset(PsqlScanState scan_state, bool active_branch,
return PSQL_CMD_SKIP_LINE;
}
+/*
+ * \restrict -- enter "restricted mode" with the provided key
+ */
+static backslashResult
+exec_command_restrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd)
+{
+ if (active_branch)
+ {
+ char *opt;
+
+ Assert(!restricted);
+
+ opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+ if (opt == NULL || opt[0] == '\0')
+ {
+ pg_log_error("\\%s: missing required argument", cmd);
+ return PSQL_CMD_ERROR;
+ }
+
+ restrict_key = pstrdup(opt);
+ restricted = true;
+ }
+ else
+ ignore_slash_options(scan_state);
+
+ return PSQL_CMD_SKIP_LINE;
+}
+
/*
* \s -- save history in a file or show it on the screen
*/
@@ -2631,6 +2681,46 @@ exec_command_timing(PsqlScanState scan_state, bool active_branch)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
+/*
+ * \unrestrict -- exit "restricted mode" if provided key matches
+ */
+static backslashResult
+exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
+ const char *cmd)
+{
+ if (active_branch)
+ {
+ char *opt;
+
+ opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+ if (opt == NULL || opt[0] == '\0')
+ {
+ pg_log_error("\\%s: missing required argument", cmd);
+ return PSQL_CMD_ERROR;
+ }
+
+ if (!restricted)
+ {
+ pg_log_error("\\%s: not currently in restricted mode", cmd);
+ return PSQL_CMD_ERROR;
+ }
+ else if (strcmp(opt, restrict_key) == 0)
+ {
+ pfree(restrict_key);
+ restricted = false;
+ }
+ else
+ {
+ pg_log_error("\\%s: wrong key", cmd);
+ return PSQL_CMD_ERROR;
+ }
+ }
+ else
+ ignore_slash_options(scan_state);
+
+ return PSQL_CMD_SKIP_LINE;
+}
+
/*
* \unset -- unset variable
*/
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index b5c192c3cdd..110e694e46e 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -200,6 +200,10 @@ slashUsage(unsigned short int pager)
HELP0(" \\gset [PREFIX] execute query and store result in psql variables\n");
HELP0(" \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n");
HELP0(" \\q quit psql\n");
+ HELP0(" \\restrict RESTRICT_KEY\n"
+ " enter restricted mode with provided key\n");
+ HELP0(" \\unrestrict RESTRICT_KEY\n"
+ " exit restricted mode if key matches\n");
HELP0(" \\watch [[i=]SEC] [c=N] execute query every SEC seconds, up to N times\n");
HELP0("\n");
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 3599cc1ef09..f5ec8fab1f3 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -388,4 +388,11 @@ psql_fails_like(
qr/iteration count is specified more than once/,
'\watch, iteration count is specified more than once');
+psql_fails_like(
+ $node,
+ qq{\\restrict test
+\\! should_fail},
+ qr/backslash commands are restricted; only \\unrestrict is allowed/,
+ 'meta-command in restrict mode fails');
+
done_testing();
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 23d89d0720f..585a79293d9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1733,10 +1733,10 @@ psql_completion(const char *text, int start, int end)
"\\out",
"\\password", "\\print", "\\prompt", "\\pset",
"\\qecho", "\\quit",
- "\\reset",
+ "\\reset", "\\restrict",
"\\s", "\\set", "\\setenv", "\\sf", "\\sv",
"\\t", "\\T", "\\timing",
- "\\unset",
+ "\\unrestrict", "\\unset",
"\\x",
"\\warn", "\\watch", "\\write",
"\\z",
diff --git a/src/test/recovery/t/027_stream_regress.pl b/src/test/recovery/t/027_stream_regress.pl
index f7e46a70718..eda4e6904cb 100644
--- a/src/test/recovery/t/027_stream_regress.pl
+++ b/src/test/recovery/t/027_stream_regress.pl
@@ -103,6 +103,7 @@ $node_primary->wait_for_replay_catchup($node_standby_1);
command_ok(
[
'pg_dumpall', '-f', $outputdir . '/primary.dump',
+ '--restrict-key=test',
'--no-sync', '-p', $node_primary->port,
'--no-unlogged-table-data' # if unlogged, standby has schema only
],
@@ -110,6 +111,7 @@ command_ok(
command_ok(
[
'pg_dumpall', '-f', $outputdir . '/standby.dump',
+ '--restrict-key=test',
'--no-sync', '-p', $node_standby_1->port
],
'dump standby server');
@@ -128,6 +130,7 @@ command_ok(
('--schema', 'pg_catalog'),
('-f', $outputdir . '/catalogs_primary.dump'),
'--no-sync',
+ '--restrict-key=test',
('-p', $node_primary->port),
'--no-unlogged-table-data',
'regression'
@@ -139,6 +142,7 @@ command_ok(
('--schema', 'pg_catalog'),
('-f', $outputdir . '/catalogs_standby.dump'),
'--no-sync',
+ '--restrict-key=test',
('-p', $node_standby_1->port),
'regression'
],
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7cd0c27cca8..c28548da4da 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4537,6 +4537,7 @@ invalid command \lo
\pset arg1 arg2
\q
\reset
+ \restrict test
\s arg1
\set arg1 arg2 arg3 arg4 arg5 arg6 arg7
\setenv arg1 arg2
@@ -4545,6 +4546,7 @@ invalid command \lo
\t arg1
\T arg1
\timing arg1
+ \unrestrict not_valid
\unset arg1
\w arg1
\watch arg1 arg2
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index f3bc6cd07e8..25ae4507f13 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1020,6 +1020,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\pset arg1 arg2
\q
\reset
+ \restrict test
\s arg1
\set arg1 arg2 arg3 arg4 arg5 arg6 arg7
\setenv arg1 arg2
@@ -1028,6 +1029,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\t arg1
\T arg1
\timing arg1
+ \unrestrict not_valid
\unset arg1
\w arg1
\watch arg1 arg2