From: Noah Misch Date: Mon, 8 Aug 2016 14:07:46 +0000 (-0400) Subject: Introduce a psql "\connect -reuse-previous=on|off" option. X-Git-Tag: REL9_6_BETA4~12 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9d924e9a64b91571e04252424c01210fc0f6f6d9;p=postgresql Introduce a psql "\connect -reuse-previous=on|off" option. The decision to reuse values of parameters from a previous connection has been based on whether the new target is a conninfo string. Add this means of overriding that default. This feature arose as one component of a fix for security vulnerabilities in pg_dump, pg_dumpall, and pg_upgrade, so back-patch to 9.1 (all supported versions). In 9.3 and later, comment paragraphs that required update had already-incorrect claims about behavior when no connection is open; fix those problems. Security: CVE-2016-5424 --- diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index a4d3e429f6..d9bce25f33 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -825,7 +825,7 @@ testdb=> - \c or \connect [ dbname [ username ] [ host ] [ port ] ] | conninfo + \c or \connect [ -reuse-previous=on|off ] [ dbname [ username ] [ host ] [ port ] | conninfo ] Establishes a new connection to a PostgreSQL @@ -835,16 +835,19 @@ testdb=> - When using positional parameters, if any of - dbname, + Where the command omits database name, user, host, or port, the new + connection can reuse values from the previous connection. By default, + values from the previous connection are reused except when processing + a conninfo string. Passing a first argument + of -reuse-previous=on + or -reuse-previous=off overrides that default. + When the command neither specifies nor reuses a particular parameter, + the libpq default is used. Specifying any + of dbname, username, host or - port are omitted or - specified as -, the value of that parameter from - the previous connection is used; if there is no previous connection, - the libpq default for the parameter's value - is used. When using conninfo strings, no values from the - previous connection are used for the new connection. + port + as - is equivalent to omitting that parameter. diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 3f2cebf3c6..1608bf414c 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -65,7 +65,8 @@ static backslashResult exec_command(const char *cmd, PQExpBuffer query_buf); static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool *edited); -static bool do_connect(char *dbname, char *user, char *host, char *port); +static bool do_connect(enum trivalue reuse_previous_specification, + char *dbname, char *user, char *host, char *port); static bool do_shell(const char *command); static bool do_watch(PQExpBuffer query_buf, double sleep); static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, @@ -231,12 +232,9 @@ exec_command(const char *cmd, /* * \c or \connect -- connect to database using the specified parameters. * - * \c dbname user host port + * \c [-reuse-previous=BOOL] dbname user host port * - * If any of these parameters are omitted or specified as '-', the current - * value of the parameter will be used instead. If the parameter has no - * current value, the default value for that parameter will be used. Some - * examples: + * Specifying a parameter as '-' is equivalent to omitting it. Examples: * * \c - - hst Connect to current database on current port of host * "hst" as current user. \c - usr - prt Connect to current database on @@ -245,17 +243,31 @@ exec_command(const char *cmd, */ else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) { + static const char prefix[] = "-reuse-previous="; char *opt1, *opt2, *opt3, *opt4; + enum trivalue reuse_previous; opt1 = read_connect_arg(scan_state); + if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0) + { + reuse_previous = + ParseVariableBool(opt1 + sizeof(prefix) - 1, prefix) ? + TRI_YES : TRI_NO; + + free(opt1); + opt1 = read_connect_arg(scan_state); + } + else + reuse_previous = TRI_DEFAULT; + opt2 = read_connect_arg(scan_state); opt3 = read_connect_arg(scan_state); opt4 = read_connect_arg(scan_state); - success = do_connect(opt1, opt2, opt3, opt4); + success = do_connect(reuse_previous, opt1, opt2, opt3, opt4); free(opt1); free(opt2); @@ -1754,22 +1766,25 @@ param_is_newly_set(const char *old_val, const char *new_val) /* * do_connect -- handler for \connect * - * Connects to a database with given parameters. If there exists an - * established connection, NULL values will be replaced with the ones - * in the current connection. Otherwise NULL will be passed for that - * parameter to PQconnectdbParams(), so the libpq defaults will be used. + * Connects to a database with given parameters. Absent an established + * connection, all parameters are required. Given -reuse-previous=off or a + * connection string without -reuse-previous=on, NULL values will pass through + * to PQconnectdbParams(), so the libpq defaults will be used. Otherwise, NULL + * values will be replaced with the ones in the current connection. * * In interactive mode, if connection fails with the given parameters, * the old connection will be kept. */ static bool -do_connect(char *dbname, char *user, char *host, char *port) +do_connect(enum trivalue reuse_previous_specification, + char *dbname, char *user, char *host, char *port) { PGconn *o_conn = pset.db, *n_conn; char *password = NULL; bool keep_password; bool has_connection_string; + bool reuse_previous; if (!o_conn && (!dbname || !user || !host || !port)) { @@ -1783,17 +1798,36 @@ do_connect(char *dbname, char *user, char *host, char *port) return false; } - /* grab values from the old connection, unless supplied by caller */ - if (!user) + has_connection_string = dbname ? + recognized_connection_string(dbname) : false; + switch (reuse_previous_specification) + { + case TRI_YES: + reuse_previous = true; + break; + case TRI_NO: + reuse_previous = false; + break; + default: + reuse_previous = !has_connection_string; + break; + } + /* Silently ignore arguments subsequent to a connection string. */ + if (has_connection_string) + { + user = NULL; + host = NULL; + port = NULL; + } + + /* grab missing values from the old connection */ + if (!user && reuse_previous) user = PQuser(o_conn); - if (!host) + if (!host && reuse_previous) host = PQhost(o_conn); - if (!port) + if (!port && reuse_previous) port = PQport(o_conn); - has_connection_string = - dbname ? recognized_connection_string(dbname) : false; - /* * Any change in the parameters read above makes us discard the password. * We also discard it if we're to use a conninfo rather than the @@ -1808,10 +1842,10 @@ do_connect(char *dbname, char *user, char *host, char *port) (port && PQport(o_conn) && strcmp(port, PQport(o_conn)) == 0); /* - * Grab dbname from old connection unless supplied by caller. No password - * discard if this changes: passwords aren't (usually) database-specific. + * Grab missing dbname from old connection. No password discard if this + * changes: passwords aren't (usually) database-specific. */ - if (!dbname) + if (!dbname && reuse_previous) dbname = PQdb(o_conn); /* @@ -1842,20 +1876,27 @@ do_connect(char *dbname, char *user, char *host, char *port) #define PARAMS_ARRAY_SIZE 8 const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); - int paramnum = 0; + int paramnum = -1; - keywords[0] = "dbname"; - values[0] = dbname; + keywords[++paramnum] = "host"; + values[paramnum] = host; + keywords[++paramnum] = "port"; + values[paramnum] = port; + keywords[++paramnum] = "user"; + values[paramnum] = user; - if (!has_connection_string) - { - keywords[++paramnum] = "host"; - values[paramnum] = host; - keywords[++paramnum] = "port"; - values[paramnum] = port; - keywords[++paramnum] = "user"; - values[paramnum] = user; - } + /* + * Position in the array matters when the dbname is a connection + * string, because settings in a connection string override earlier + * array entries only. Thus, user= in the connection string always + * takes effect, but client_encoding= often will not. + * + * If you change this code, also change the initial-connection code in + * main(). For no good reason, a connection string password= takes + * precedence in main() but not here. + */ + keywords[++paramnum] = "dbname"; + values[paramnum] = dbname; keywords[++paramnum] = "password"; values[paramnum] = password; keywords[++paramnum] = "fallback_application_name"; diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index b96cdc445e..111593cd9d 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -227,7 +227,7 @@ main(int argc, char *argv[]) values[2] = options.username; keywords[3] = "password"; values[3] = password; - keywords[4] = "dbname"; + keywords[4] = "dbname"; /* see do_connect() */ values[4] = (options.list_dbs && options.dbname == NULL) ? "postgres" : options.dbname; keywords[5] = "fallback_application_name";