diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/pg_combinebackup/t/002_compare_backups.pl | 2 | ||||
-rw-r--r-- | src/bin/pg_dump/dumputils.c | 38 | ||||
-rw-r--r-- | src/bin/pg_dump/dumputils.h | 3 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_backup.h | 4 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_backup_archiver.c | 32 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dump.c | 21 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dumpall.c | 36 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_restore.c | 23 | ||||
-rw-r--r-- | src/bin/pg_dump/t/002_pg_dump.pl | 26 | ||||
-rw-r--r-- | src/bin/pg_upgrade/t/002_pg_upgrade.pl | 3 | ||||
-rw-r--r-- | src/bin/psql/command.c | 94 | ||||
-rw-r--r-- | src/bin/psql/help.c | 4 | ||||
-rw-r--r-- | src/bin/psql/t/001_basic.pl | 7 | ||||
-rw-r--r-- | src/bin/psql/tab-complete.in.c | 4 | ||||
-rw-r--r-- | src/test/recovery/t/027_stream_regress.pl | 4 | ||||
-rw-r--r-- | src/test/regress/expected/psql.out | 2 | ||||
-rw-r--r-- | src/test/regress/sql/psql.sql | 2 |
17 files changed, 292 insertions, 13 deletions
diff --git a/src/bin/pg_combinebackup/t/002_compare_backups.pl b/src/bin/pg_combinebackup/t/002_compare_backups.pl index 2c7ca89b92f..9c7fe56cb7c 100644 --- a/src/bin/pg_combinebackup/t/002_compare_backups.pl +++ b/src/bin/pg_combinebackup/t/002_compare_backups.pl @@ -174,6 +174,7 @@ my $dump2 = $backupdir . '/pitr2.dump'; $pitr1->command_ok( [ 'pg_dumpall', + '--restrict-key=test', '--no-sync', '--no-unlogged-table-data', '--file' => $dump1, @@ -183,6 +184,7 @@ $pitr1->command_ok( $pitr2->command_ok( [ 'pg_dumpall', + '--restrict-key=test', '--no-sync', '--no-unlogged-table-data', '--file' => $dump2, diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 4c02a9863c4..8945bdd42c5 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -21,6 +21,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, @@ -957,3 +958,40 @@ create_or_open_dir(const char *dirname) pg_fatal("directory \"%s\" is not empty", dirname); } } + +/* + * 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 71dd3f12a8d..10f6e27c0a0 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -65,4 +65,7 @@ extern void makeAlterConfigCommand(PGconn *conn, const char *configitem, PQExpBuffer buf); extern void create_or_open_dir(const char *dirname); +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 4ebef1e8644..d9041dad720 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -163,6 +163,8 @@ typedef struct _restoreOptions bool dumpSchema; bool dumpData; bool dumpStatistics; + + char *restrict_key; } RestoreOptions; typedef struct _dumpOptions @@ -213,6 +215,8 @@ typedef struct _dumpOptions bool dumpSchema; bool dumpData; bool dumpStatistics; + + 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 248e4151552..3c3acbaccdb 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -197,6 +197,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; } @@ -461,6 +462,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); @@ -802,6 +814,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. */ AH->stage = STAGE_FINALIZING; @@ -3452,11 +3472,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 66380a6d301..fc7a6639163 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -537,6 +537,7 @@ main(int argc, char **argv) {"filter", required_argument, NULL, 16}, {"exclude-extension", required_argument, NULL, 17}, {"sequence-data", no_argument, &dopt.sequence_data, 1}, + {"restrict-key", required_argument, NULL, 25}, {NULL, 0, NULL, 0} }; @@ -797,6 +798,10 @@ main(int argc, char **argv) with_statistics = true; 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); @@ -889,8 +894,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 @@ -1229,6 +1248,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; @@ -1340,6 +1360,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(_(" --sequence-data include sequence data in dump\n")); diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 3e7f930c940..bb451c1bae1 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -122,6 +122,8 @@ static char *filename = NULL; static SimpleStringList database_exclude_patterns = {NULL, NULL}; static SimpleStringList database_exclude_names = {NULL, NULL}; +static char *restrict_key; + int main(int argc, char *argv[]) { @@ -184,6 +186,7 @@ main(int argc, char *argv[]) {"statistics-only", no_argument, &statistics_only, 1}, {"filter", required_argument, NULL, 8}, {"sequence-data", no_argument, &sequence_data, 1}, + {"restrict-key", required_argument, NULL, 9}, {NULL, 0, NULL, 0} }; @@ -371,6 +374,12 @@ main(int argc, char *argv[]) read_dumpall_filters(optarg, &database_exclude_patterns); 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); @@ -481,6 +490,16 @@ main(int argc, char *argv[]) appendPQExpBufferStr(pgdumpopts, " --sequence-data"); /* + * 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 * "template1". @@ -571,6 +590,16 @@ main(int argc, char *argv[]) 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 * database. Everything we're restoring here is a global, so whichever @@ -630,6 +659,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); @@ -702,6 +737,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(_(" --sequence-data include sequence data in dump\n")); printf(_(" --statistics dump the statistics\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index 6c129278bc5..c9776306c5c 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -45,6 +45,7 @@ #include <termios.h> #endif +#include "dumputils.h" #include "fe_utils/option_utils.h" #include "filter.h" #include "getopt_long.h" @@ -140,6 +141,7 @@ main(int argc, char **argv) {"statistics", no_argument, &with_statistics, 1}, {"statistics-only", no_argument, &statistics_only, 1}, {"filter", required_argument, NULL, 4}, + {"restrict-key", required_argument, NULL, 6}, {NULL, 0, NULL, 0} }; @@ -315,6 +317,10 @@ main(int argc, char **argv) opts->exit_on_error = true; 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); @@ -350,8 +356,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"); + } /* reject conflicting "-only" options */ if (data_only && schema_only) @@ -546,6 +568,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(_(" --statistics restore the statistics\n")); printf(_(" --statistics-only restore only the statistics, not schema or data\n")); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index e3257231a9e..e7a2d64f741 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -881,6 +881,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 @@ -4239,7 +4249,6 @@ my %tests = ( }, 'ALTER TABLE measurement PRIMARY KEY' => { - all_runs => 1, catch_all => 'CREATE ... commands', create_order => 93, create_sql => @@ -4291,7 +4300,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 @@ -5424,9 +5432,10 @@ foreach my $run (sort keys %pgdump_runs) # Check for proper test definitions # - # There should be a "like" list, even if it is empty. (This - # makes the test more self-documenting.) - if (!defined($tests{$test}->{like})) + # Either "all_runs" should be set or there should be a "like" list, + # even if it is empty. (This makes the test more self-documenting.) + if (!defined($tests{$test}->{all_runs}) + && !defined($tests{$test}->{like})) { die "missing \"like\" in test \"$test\""; } @@ -5462,9 +5471,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 0b15e38297e..4b5e895809b 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -86,6 +86,7 @@ sub get_dump_for_comparison $node->run_log( [ 'pg_dump', '--no-sync', + '--restrict-key=test', '-d' => $node->connstr($db), '-f' => $dumpfile ]); @@ -427,6 +428,7 @@ SKIP: # that we need to use pg_dumpall from the new node here. my @dump_command = ( 'pg_dumpall', '--no-sync', + '--restrict-key=test', '--dbname' => $oldnode->connstr('postgres'), '--file' => $dump1_file); # --extra-float-digits is needed when upgrading from a version older than 11. @@ -624,6 +626,7 @@ is( $result, # Second dump from the upgraded instance. @dump_command = ( 'pg_dumpall', '--no-sync', + '--restrict-key=test', '--dbname' => $newnode->connstr('postgres'), '--file' => $dump2_file); # --extra-float-digits is needed when upgrading from a version older than 11. diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 0e00d73487c..cc602087db2 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -130,6 +130,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_sendpipeline(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch); @@ -142,6 +144,8 @@ static backslashResult exec_command_syncpipeline(PsqlScanState scan_state, bool 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, @@ -192,6 +196,8 @@ static char *pset_value_string(const char *param, printQueryOpt *popt); static void checkWin32Codepage(void); #endif +static bool restricted; +static char *restrict_key; /*---------- @@ -237,8 +243,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) { @@ -416,6 +433,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, "sendpipeline") == 0) @@ -438,6 +457,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) @@ -2755,6 +2776,35 @@ exec_command_reset(PsqlScanState scan_state, bool active_branch, } /* + * \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 */ static backslashResult @@ -3136,6 +3186,46 @@ exec_command_timing(PsqlScanState scan_state, bool active_branch) } /* + * \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 */ static backslashResult diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 8c62729a0d1..ed00c36695e 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -171,6 +171,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] [m=MIN]\n" " execute query every SEC seconds, up to N times,\n" " stop if less than MIN rows are returned\n"); diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl index f42c3961e09..cf07a9dbd5e 100644 --- a/src/bin/psql/t/001_basic.pl +++ b/src/bin/psql/t/001_basic.pl @@ -530,4 +530,11 @@ psql_fails_like( qr/COPY in a pipeline is not supported, aborting connection/, '\copy to in pipeline: fails'); +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.in.c b/src/bin/psql/tab-complete.in.c index 1f2ca946fc5..8b10f2313f3 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -1918,11 +1918,11 @@ psql_completion(const char *text, int start, int end) "\\out", "\\parse", "\\password", "\\print", "\\prompt", "\\pset", "\\qecho", "\\quit", - "\\reset", + "\\reset", "\\restrict", "\\s", "\\sendpipeline", "\\set", "\\setenv", "\\sf", "\\startpipeline", "\\sv", "\\syncpipeline", "\\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 5d2c06ba06e..12d8852fb4b 100644 --- a/src/test/recovery/t/027_stream_regress.pl +++ b/src/test/recovery/t/027_stream_regress.pl @@ -117,6 +117,7 @@ command_ok( 'pg_dumpall', '--file' => $outputdir . '/primary.dump', '--no-sync', '--no-statistics', + '--restrict-key=test', '--port' => $node_primary->port, '--no-unlogged-table-data', # if unlogged, standby has schema only ], @@ -126,6 +127,7 @@ command_ok( 'pg_dumpall', '--file' => $outputdir . '/standby.dump', '--no-sync', '--no-statistics', + '--restrict-key=test', '--port' => $node_standby_1->port, ], 'dump standby server'); @@ -145,6 +147,7 @@ command_ok( '--schema' => 'pg_catalog', '--file' => $outputdir . '/catalogs_primary.dump', '--no-sync', + '--restrict-key=test', '--port', $node_primary->port, '--no-unlogged-table-data', 'regression', @@ -156,6 +159,7 @@ command_ok( '--schema' => 'pg_catalog', '--file' => $outputdir . '/catalogs_standby.dump', '--no-sync', + '--restrict-key=test', '--port' => $node_standby_1->port, 'regression', ], diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 236eba2540e..a79325e8a2f 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -4705,6 +4705,7 @@ invalid command \lo \pset arg1 arg2 \q \reset + \restrict test \s arg1 \sendpipeline \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 @@ -4716,6 +4717,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 e2e31245439..f064e4f5456 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1073,6 +1073,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; \pset arg1 arg2 \q \reset + \restrict test \s arg1 \sendpipeline \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 @@ -1084,6 +1085,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 |