diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml index 3a2504cd0a..dfd7b7c405 100644 --- a/doc/src/sgml/ref/alter_role.sgml +++ b/doc/src/sgml/ref/alter_role.sgml @@ -1,5 +1,5 @@ @@ -24,7 +24,7 @@ PostgreSQL documentation ALTER ROLE name [ [ WITH ] option [ ... ] ] where option can be: - + SUPERUSER | NOSUPERUSER | CREATEDB | NOCREATEDB | CREATEROLE | NOCREATEROLE @@ -33,7 +33,7 @@ ALTER ROLE name [ [ WITH ] connlimit | [ ENCRYPTED | UNENCRYPTED ] PASSWORD 'password' - | VALID UNTIL 'timestamp' + | VALID UNTIL 'timestamp' ALTER ROLE name RENAME TO new_name @@ -54,7 +54,7 @@ ALTER ROLE name [ IN DATABASE The first variant of this command listed in the synopsis can change - many of the role attributes that can be specified in + many of the role attributes that can be specified in . (All the possible attributes are covered, except that there are no options for adding or removing memberships; use @@ -79,20 +79,24 @@ ALTER ROLE name [ IN DATABASE MD5-encrypted. - - The remaining variants change a role's session default for a configuration variable - for all databases or, when the IN DATABASE clause is specified, - for the named database. Whenever the role subsequently + + The remaining variants change a role's session default for a configuration + variable, either for all databases or, when the IN + DATABASE clause is specified, only for sessions in + the named database. Whenever the role subsequently starts a new session, the specified value becomes the session default, overriding whatever setting is present in postgresql.conf or has been received from the postgres - command line. This only happens at login time, so configuration - settings associated with a role to which you've will be ignored. Settings set to - a role directly are overridden by any database specific settings attached to a role. + command line. This only happens at login time; executing + or + does not cause new + configuration values to be set. + Settings set for all databases are overridden by database-specific settings + attached to a role. Superusers can change anyone's session defaults. Roles having CREATEROLE privilege can change defaults for non-superuser - roles. Certain variables cannot be set this way, or can only be + roles. Ordinary roles can only set defaults for themselves. + Certain configuration variables cannot be set this way, or can only be set if a superuser issues the command. @@ -169,14 +173,15 @@ ALTER ROLE name [ IN DATABASE RESET ALL to clear all role-specific settings. SET FROM CURRENT saves the session's current value of the parameter as the role-specific value. - If used in conjunction with IN DATABASE, the configuration + If IN DATABASE is specified, the configuration parameter is set or removed for the given role and database only. - Role-specific variable setting take effect only at login; - - does not process role-specific variable settings. + Role-specific variable settings take effect only at login; + and + + do not process role-specific variable settings. @@ -210,8 +215,8 @@ ALTER ROLE name [ IN DATABASE contains a command - \password that can be used to safely change a - role's password. + \password that can be used to change a + role's password without exposing the cleartext password. @@ -276,8 +281,8 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000; - Give a role a non-default, database-specific setting of the - parameter: + Give a role a non-default, database-specific setting of the + parameter: ALTER ROLE fred IN DATABASE devel SET client_min_messages = DEBUG; @@ -287,7 +292,7 @@ ALTER ROLE fred IN DATABASE devel SET client_min_messages = DEBUG; Compatibility - + The ALTER ROLE statement is a PostgreSQL extension. diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 15ca9c1b22..98261e10e4 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut . * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.549 2010/04/20 11:15:06 rhaas Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.550 2010/04/21 20:54:19 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -2864,6 +2864,8 @@ static void ShowGUCConfigOption(const char *name, DestReceiver *dest); static void ShowAllGUCConfig(DestReceiver *dest); static char *_ShowOption(struct config_generic * record, bool use_units); static bool is_newvalue_equal(struct config_generic * record, const char *newvalue); +static bool validate_option_array_item(const char *name, const char *value, + bool skipIfNoPermissions); /* @@ -5474,14 +5476,15 @@ flatten_set_variable_args(const char *name, List *args) if (args == NIL) return NULL; - /* Else get flags for the variable */ - record = find_option(name, true, ERROR); - if (record == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("unrecognized configuration parameter \"%s\"", name))); - - flags = record->flags; + /* + * Get flags for the variable; if it's not known, use default flags. + * (Caller might throw error later, but not our business to do so here.) + */ + record = find_option(name, false, WARNING); + if (record) + flags = record->flags; + else + flags = 0; /* Complain if list input and non-list variable */ if ((flags & GUC_LIST_INPUT) == 0 && @@ -5870,12 +5873,27 @@ define_custom_variable(struct config_generic * variable) else phcontext = PGC_SIGHUP; break; + case PGC_S_DATABASE: case PGC_S_USER: case PGC_S_DATABASE_USER: + /* + * The existing value came from an ALTER ROLE/DATABASE SET command. + * We can assume that at the time the command was issued, we + * checked that the issuing user was superuser if the variable + * requires superuser privileges to set. So it's safe to + * use SUSET context here. + */ + phcontext = PGC_SUSET; + break; + case PGC_S_CLIENT: case PGC_S_SESSION: default: + /* + * We must assume that the value came from an untrusted user, + * even if the current_user is a superuser. + */ phcontext = PGC_USERSET; break; } @@ -7180,7 +7198,7 @@ ProcessGUCArray(ArrayType *array, ArrayType * GUCArrayAdd(ArrayType *array, const char *name, const char *value) { - const char *varname; + struct config_generic *record; Datum datum; char *newval; ArrayType *a; @@ -7188,15 +7206,15 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) Assert(name); Assert(value); - /* test if the option is valid */ - set_config_option(name, value, - superuser() ? PGC_SUSET : PGC_USERSET, - PGC_S_TEST, GUC_ACTION_SET, false); + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, value, false); - /* convert name to canonical spelling, so we can use plain strcmp */ - (void) GetConfigOptionByName(name, &varname); - name = varname; + /* normalize name (converts obsolete GUC names to modern spellings) */ + record = find_option(name, false, WARNING); + if (record) + name = record->name; + /* build new item for array */ newval = palloc(strlen(name) + 1 + strlen(value) + 1); sprintf(newval, "%s=%s", name, value); datum = CStringGetTextDatum(newval); @@ -7227,6 +7245,8 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) if (isnull) continue; current = TextDatumGetCString(d); + + /* check for match up through and including '=' */ if (strncmp(current, newval, strlen(name) + 1) == 0) { index = i; @@ -7259,21 +7279,20 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) ArrayType * GUCArrayDelete(ArrayType *array, const char *name) { - const char *varname; + struct config_generic *record; ArrayType *newarray; int i; int index; Assert(name); - /* test if the option is valid */ - set_config_option(name, NULL, - superuser() ? PGC_SUSET : PGC_USERSET, - PGC_S_TEST, GUC_ACTION_SET, false); + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, NULL, false); - /* convert name to canonical spelling, so we can use plain strcmp */ - (void) GetConfigOptionByName(name, &varname); - name = varname; + /* normalize name (converts obsolete GUC names to modern spellings) */ + record = find_option(name, false, WARNING); + if (record) + name = record->name; /* if array is currently null, then surely nothing to delete */ if (!array) @@ -7303,10 +7322,8 @@ GUCArrayDelete(ArrayType *array, const char *name) && val[strlen(name)] == '=') continue; - /* else add it to the output array */ if (newarray) - { newarray = array_set(newarray, 1, &index, d, false, @@ -7314,7 +7331,6 @@ GUCArrayDelete(ArrayType *array, const char *name) -1 /* TEXT's typlen */ , false /* TEXT's typbyval */ , 'i' /* TEXT's typalign */ ); - } else newarray = construct_array(&d, 1, TEXTOID, @@ -7326,6 +7342,7 @@ GUCArrayDelete(ArrayType *array, const char *name) return newarray; } + /* * Given a GUC array, delete all settings from it that our permission * level allows: if superuser, delete them all; if regular user, only @@ -7342,7 +7359,7 @@ GUCArrayReset(ArrayType *array) if (!array) return NULL; - /* if we're superuser, we can delete everything */ + /* if we're superuser, we can delete everything, so just do it */ if (superuser()) return NULL; @@ -7355,7 +7372,6 @@ GUCArrayReset(ArrayType *array) char *val; char *eqsgn; bool isnull; - struct config_generic *gconf; d = array_ref(array, 1, &i, -1 /* varlenarray */ , @@ -7363,7 +7379,6 @@ GUCArrayReset(ArrayType *array) false /* TEXT's typbyval */ , 'i' /* TEXT's typalign */ , &isnull); - if (isnull) continue; val = TextDatumGetCString(d); @@ -7371,20 +7386,12 @@ GUCArrayReset(ArrayType *array) eqsgn = strchr(val, '='); *eqsgn = '\0'; - gconf = find_option(val, false, WARNING); - if (!gconf) + /* skip if we have permission to delete it */ + if (validate_option_array_item(val, NULL, true)) continue; - /* note: superuser-ness was already checked above */ - /* skip entry if OK to delete */ - if (gconf->context == PGC_USERSET) - continue; - - /* XXX do we need to worry about database owner? */ - /* else add it to the output array */ if (newarray) - { newarray = array_set(newarray, 1, &index, d, false, @@ -7392,7 +7399,6 @@ GUCArrayReset(ArrayType *array) -1 /* TEXT's typlen */ , false /* TEXT's typbyval */ , 'i' /* TEXT's typalign */ ); - } else newarray = construct_array(&d, 1, TEXTOID, @@ -7405,6 +7411,89 @@ GUCArrayReset(ArrayType *array) return newarray; } +/* + * Validate a proposed option setting for GUCArrayAdd/Delete/Reset. + * + * name is the option name. value is the proposed value for the Add case, + * or NULL for the Delete/Reset cases. If skipIfNoPermissions is true, it's + * not an error to have no permissions to set the option. + * + * Returns TRUE if OK, FALSE if skipIfNoPermissions is true and user does not + * have permission to change this option (all other error cases result in an + * error being thrown). + */ +static bool +validate_option_array_item(const char *name, const char *value, + bool skipIfNoPermissions) + +{ + struct config_generic *gconf; + + /* + * There are three cases to consider: + * + * name is a known GUC variable. Check the value normally, check + * permissions normally (ie, allow if variable is USERSET, or if it's + * SUSET and user is superuser). + * + * name is not known, but exists or can be created as a placeholder + * (implying it has a prefix listed in custom_variable_classes). + * We allow this case if you're a superuser, otherwise not. Superusers + * are assumed to know what they're doing. We can't allow it for other + * users, because when the placeholder is resolved it might turn out to + * be a SUSET variable; define_custom_variable assumes we checked that. + * + * name is not known and can't be created as a placeholder. Throw error, + * unless skipIfNoPermissions is true, in which case return FALSE. + * (It's tempting to allow this case to superusers, if the name is + * qualified but not listed in custom_variable_classes. That would + * ease restoring of dumps containing ALTER ROLE/DATABASE SET. However, + * it's not clear that this usage justifies such a loss of error checking. + * You can always fix custom_variable_classes before you restore.) + */ + gconf = find_option(name, true, WARNING); + if (!gconf) + { + /* not known, failed to make a placeholder */ + if (skipIfNoPermissions) + return false; + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized configuration parameter \"%s\"", name))); + } + + if (gconf->flags & GUC_CUSTOM_PLACEHOLDER) + { + /* + * We cannot do any meaningful check on the value, so only permissions + * are useful to check. + */ + if (superuser()) + return true; + if (skipIfNoPermissions) + return false; + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", name))); + } + + /* manual permissions check so we can avoid an error being thrown */ + if (gconf->context == PGC_USERSET) + /* ok */ ; + else if (gconf->context == PGC_SUSET && superuser()) + /* ok */ ; + else if (skipIfNoPermissions) + return false; + /* if a permissions error should be thrown, let set_config_option do it */ + + /* test for permissions and valid option value */ + set_config_option(name, value, + superuser() ? PGC_SUSET : PGC_USERSET, + PGC_S_TEST, GUC_ACTION_SET, false); + + return true; +} + /* * assign_hook and show_hook subroutines