diff options
Diffstat (limited to 'src/backend/commands/user.c')
-rw-r--r-- | src/backend/commands/user.c | 1525 |
1 files changed, 0 insertions, 1525 deletions
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c deleted file mode 100644 index fef3049a958..00000000000 --- a/src/backend/commands/user.c +++ /dev/null @@ -1,1525 +0,0 @@ -/*------------------------------------------------------------------------- - * - * user.c - * Commands for manipulating users and groups. - * - * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.105 2002/06/20 20:29:27 momjian Exp $ - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <errno.h> -#include <unistd.h> - -#include "access/heapam.h" -#include "catalog/catname.h" -#include "catalog/pg_database.h" -#include "catalog/pg_shadow.h" -#include "catalog/pg_group.h" -#include "catalog/indexing.h" -#include "commands/user.h" -#include "libpq/crypt.h" -#include "miscadmin.h" -#include "storage/pmsignal.h" -#include "utils/acl.h" -#include "utils/array.h" -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/guc.h" -#include "utils/lsyscache.h" -#include "utils/syscache.h" - - -extern bool Password_encryption; - -static void CheckPgUserAclNotNull(void); -static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple, - List *members); -static IdList *IdListToArray(List *members); -static List *IdArrayToList(IdList *oldarray); - - -/* - * fputs_quote - * - * Outputs string in quotes, with double-quotes duplicated. - * We could use quote_ident(), but that expects varlena. - */ -static void fputs_quote(char *str, FILE *fp) -{ - fputc('"', fp); - while (*str) - { - fputc(*str, fp); - if (*str == '"') - fputc('"', fp); - str++; - } - fputc('"', fp); -} - - - -/* - * group_getfilename --- get full pathname of group file - * - * Note that result string is palloc'd, and should be freed by the caller. - */ -char * -group_getfilename(void) -{ - int bufsize; - char *pfnam; - - bufsize = strlen(DataDir) + strlen("/global/") + - strlen(USER_GROUP_FILE) + 1; - pfnam = (char *) palloc(bufsize); - snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_GROUP_FILE); - - return pfnam; -} - - - -/* - * Get full pathname of password file. - * Note that result string is palloc'd, and should be freed by the caller. - */ -char * -user_getfilename(void) -{ - int bufsize; - char *pfnam; - - bufsize = strlen(DataDir) + strlen("/global/") + - strlen(PWD_FILE) + 1; - pfnam = (char *) palloc(bufsize); - snprintf(pfnam, bufsize, "%s/global/%s", DataDir, PWD_FILE); - - return pfnam; -} - - - -/* - * write_group_file for trigger update_pg_pwd_and_pg_group - */ -static void -write_group_file(Relation urel, Relation grel) -{ - char *filename, - *tempname; - int bufsize; - FILE *fp; - mode_t oumask; - HeapScanDesc scan; - HeapTuple tuple; - TupleDesc dsc = RelationGetDescr(grel); - - /* - * Create a temporary filename to be renamed later. This prevents the - * backend from clobbering the pg_group file while the postmaster might - * be reading from it. - */ - filename = group_getfilename(); - bufsize = strlen(filename) + 12; - tempname = (char *) palloc(bufsize); - - snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); - oumask = umask((mode_t) 077); - fp = AllocateFile(tempname, "w"); - umask(oumask); - if (fp == NULL) - elog(ERROR, "write_group_file: unable to write %s: %m", tempname); - - /* read table */ - scan = heap_beginscan(grel, SnapshotSelf, 0, NULL); - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Datum datum, grolist_datum; - bool isnull; - char *groname; - IdList *grolist_p; - AclId *aidp; - int i, j, - num; - char *usename; - bool first_user = true; - - datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull); - /* ignore NULL groupnames --- shouldn't happen */ - if (isnull) - continue; - groname = NameStr(*DatumGetName(datum)); - - /* - * Check for illegal characters in the group name. - */ - i = strcspn(groname, "\n"); - if (groname[i] != '\0') - { - elog(LOG, "Invalid group name '%s'", groname); - continue; - } - - grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull); - /* Ignore NULL group lists */ - if (isnull) - continue; - - /* be sure the IdList is not toasted */ - grolist_p = DatumGetIdListP(grolist_datum); - - /* scan grolist */ - num = IDLIST_NUM(grolist_p); - aidp = IDLIST_DAT(grolist_p); - for (i = 0; i < num; ++i) - { - tuple = SearchSysCache(SHADOWSYSID, - PointerGetDatum(aidp[i]), - 0, 0, 0); - if (HeapTupleIsValid(tuple)) - { - usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename); - - /* - * Check for illegal characters in the user name. - */ - j = strcspn(usename, "\n"); - if (usename[j] != '\0') - { - elog(LOG, "Invalid user name '%s'", usename); - continue; - } - - /* File format is: - * "dbname" "user1" "user2" "user3" - */ - if (first_user) - { - fputs_quote(groname, fp); - fputs("\t", fp); - } - else - fputs(" ", fp); - - first_user = false; - fputs_quote(usename, fp); - - ReleaseSysCache(tuple); - } - } - if (!first_user) - fputs("\n", fp); - /* if IdList was toasted, free detoasted copy */ - if ((Pointer) grolist_p != DatumGetPointer(grolist_datum)) - pfree(grolist_p); - } - heap_endscan(scan); - - fflush(fp); - if (ferror(fp)) - elog(ERROR, "%s: %m", tempname); - FreeFile(fp); - - /* - * Rename the temp file to its final name, deleting the old pg_pwd. We - * expect that rename(2) is an atomic action. - */ - if (rename(tempname, filename)) - elog(ERROR, "rename %s to %s: %m", tempname, filename); - - pfree((void *) tempname); - pfree((void *) filename); -} - - - -/* - * write_password_file for trigger update_pg_pwd_and_pg_group - * - * copy the modified contents of pg_shadow to a file used by the postmaster - * for user authentication. The file is stored as $PGDATA/global/pg_pwd. - * - * This function set is both a trigger function for direct updates to pg_shadow - * as well as being called directly from create/alter/drop user. - * - * We raise an error to force transaction rollback if we detect an illegal - * username or password --- illegal being defined as values that would - * mess up the pg_pwd parser. - */ -static void -write_user_file(Relation urel) -{ - char *filename, - *tempname; - int bufsize; - FILE *fp; - mode_t oumask; - HeapScanDesc scan; - HeapTuple tuple; - TupleDesc dsc = RelationGetDescr(urel); - - /* - * Create a temporary filename to be renamed later. This prevents the - * backend from clobbering the pg_pwd file while the postmaster might - * be reading from it. - */ - filename = user_getfilename(); - bufsize = strlen(filename) + 12; - tempname = (char *) palloc(bufsize); - - snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); - oumask = umask((mode_t) 077); - fp = AllocateFile(tempname, "w"); - umask(oumask); - if (fp == NULL) - elog(ERROR, "write_password_file: unable to write %s: %m", tempname); - - /* read table */ - scan = heap_beginscan(urel, SnapshotSelf, 0, NULL); - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Datum datum; - bool isnull; - char *usename, - *passwd, - *valuntil; - int i; - - datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull); - /* ignore NULL usernames (shouldn't happen) */ - if (isnull) - continue; - usename = NameStr(*DatumGetName(datum)); - - datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull); - - /* - * It can be argued that people having a null password shouldn't - * be allowed to connect under password authentication, because - * they need to have a password set up first. If you think - * assuming an empty password in that case is better, change this - * logic to look something like the code for valuntil. - */ - if (isnull) - continue; - - passwd = DatumGetCString(DirectFunctionCall1(textout, datum)); - - datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull); - if (isnull) - valuntil = pstrdup(""); - else - valuntil = DatumGetCString(DirectFunctionCall1(nabstimeout, datum)); - - /* - * Check for illegal characters in the username and password. - */ - i = strcspn(usename, "\n"); - if (usename[i] != '\0') - elog(ERROR, "Invalid user name '%s'", usename); - i = strcspn(passwd, "\n"); - if (passwd[i] != '\0') - elog(ERROR, "Invalid user password '%s'", passwd); - - /* - * The extra columns we emit here are not really necessary. To - * remove them, the parser in backend/libpq/crypt.c would need to - * be adjusted. - */ - fputs_quote(usename, fp); - fputs(" ", fp); - fputs_quote(passwd, fp); - fputs(" ", fp); - fputs_quote(valuntil, fp); - fputs("\n", fp); - - pfree(passwd); - pfree(valuntil); - } - heap_endscan(scan); - - fflush(fp); - if (ferror(fp)) - elog(ERROR, "%s: %m", tempname); - FreeFile(fp); - - /* - * Rename the temp file to its final name, deleting the old pg_pwd. We - * expect that rename(2) is an atomic action. - */ - if (rename(tempname, filename)) - elog(ERROR, "rename %s to %s: %m", tempname, filename); - - pfree((void *) tempname); - pfree((void *) filename); -} - - - -/* This is the wrapper for triggers. */ -Datum -update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS) -{ - /* - * ExclusiveLock ensures no one modifies pg_shadow while we read it, - * and that only one backend rewrites the flat file at a time. It's - * OK to allow normal reads of pg_shadow in parallel, however. - */ - Relation urel = heap_openr(ShadowRelationName, ExclusiveLock); - Relation grel = heap_openr(GroupRelationName, ExclusiveLock); - - write_user_file(urel); - write_group_file(urel, grel); - /* OK to release lock, since we did not modify the relation */ - heap_close(grel, ExclusiveLock); - heap_close(urel, ExclusiveLock); - - /* - * Signal the postmaster to reload its password & group-file cache. - */ - SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE); - - return PointerGetDatum(NULL); -} - - - -/* - * CREATE USER - */ -void -CreateUser(CreateUserStmt *stmt) -{ - Relation pg_shadow_rel; - TupleDesc pg_shadow_dsc; - HeapScanDesc scan; - HeapTuple tuple; - Datum new_record[Natts_pg_shadow]; - char new_record_nulls[Natts_pg_shadow]; - bool user_exists = false, - sysid_exists = false, - havesysid = false; - int max_id; - List *item, - *option; - char *password = NULL; /* PostgreSQL user password */ - bool encrypt_password = Password_encryption; /* encrypt password? */ - char encrypted_password[MD5_PASSWD_LEN + 1]; - int sysid = 0; /* PgSQL system id (valid if havesysid) */ - bool createdb = false; /* Can the user create databases? */ - bool createuser = false; /* Can this user create users? */ - List *groupElts = NIL; /* The groups the user is a member of */ - char *validUntil = NULL; /* The time the login is valid - * until */ - DefElem *dpassword = NULL; - DefElem *dsysid = NULL; - DefElem *dcreatedb = NULL; - DefElem *dcreateuser = NULL; - DefElem *dgroupElts = NULL; - DefElem *dvalidUntil = NULL; - - /* Extract options from the statement node tree */ - foreach(option, stmt->options) - { - DefElem *defel = (DefElem *) lfirst(option); - - if (strcmp(defel->defname, "password") == 0 || - strcmp(defel->defname, "encryptedPassword") == 0 || - strcmp(defel->defname, "unencryptedPassword") == 0) - { - if (dpassword) - elog(ERROR, "CREATE USER: conflicting options"); - dpassword = defel; - if (strcmp(defel->defname, "encryptedPassword") == 0) - encrypt_password = true; - else if (strcmp(defel->defname, "unencryptedPassword") == 0) - encrypt_password = false; - } - else if (strcmp(defel->defname, "sysid") == 0) - { - if (dsysid) - elog(ERROR, "CREATE USER: conflicting options"); - dsysid = defel; - } - else if (strcmp(defel->defname, "createdb") == 0) - { - if (dcreatedb) - elog(ERROR, "CREATE USER: conflicting options"); - dcreatedb = defel; - } - else if (strcmp(defel->defname, "createuser") == 0) - { - if (dcreateuser) - elog(ERROR, "CREATE USER: conflicting options"); - dcreateuser = defel; - } - else if (strcmp(defel->defname, "groupElts") == 0) - { - if (dgroupElts) - elog(ERROR, "CREATE USER: conflicting options"); - dgroupElts = defel; - } - else if (strcmp(defel->defname, "validUntil") == 0) - { - if (dvalidUntil) - elog(ERROR, "CREATE USER: conflicting options"); - dvalidUntil = defel; - } - else - elog(ERROR, "CREATE USER: option \"%s\" not recognized", - defel->defname); - } - - if (dcreatedb) - createdb = intVal(dcreatedb->arg) != 0; - if (dcreateuser) - createuser = intVal(dcreateuser->arg) != 0; - if (dsysid) - { - sysid = intVal(dsysid->arg); - if (sysid <= 0) - elog(ERROR, "user id must be positive"); - havesysid = true; - } - if (dvalidUntil) - validUntil = strVal(dvalidUntil->arg); - if (dpassword) - password = strVal(dpassword->arg); - if (dgroupElts) - groupElts = (List *) dgroupElts->arg; - - /* Check some permissions first */ - if (password) - CheckPgUserAclNotNull(); - - if (!superuser()) - elog(ERROR, "CREATE USER: permission denied"); - - if (strcmp(stmt->user, "public") == 0) - elog(ERROR, "CREATE USER: user name \"%s\" is reserved", - stmt->user); - - /* - * Scan the pg_shadow relation to be certain the user or id doesn't - * already exist. Note we secure exclusive lock, because we also need - * to be sure of what the next usesysid should be, and we need to - * protect our update of the flat password file. - */ - pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock); - pg_shadow_dsc = RelationGetDescr(pg_shadow_rel); - - scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL); - max_id = 99; /* start auto-assigned ids at 100 */ - while (!user_exists && !sysid_exists && - (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple); - int32 this_sysid; - - user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0); - - this_sysid = shadow_form->usesysid; - if (havesysid) /* customized id wanted */ - sysid_exists = (this_sysid == sysid); - else - { - /* pick 1 + max */ - if (this_sysid > max_id) - max_id = this_sysid; - } - } - heap_endscan(scan); - - if (user_exists) - elog(ERROR, "CREATE USER: user name \"%s\" already exists", - stmt->user); - if (sysid_exists) - elog(ERROR, "CREATE USER: sysid %d is already assigned", sysid); - - /* If no sysid given, use max existing id + 1 */ - if (!havesysid) - sysid = max_id + 1; - - /* - * Build a tuple to insert - */ - MemSet(new_record, 0, sizeof(new_record)); - MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); - - new_record[Anum_pg_shadow_usename - 1] = - DirectFunctionCall1(namein, CStringGetDatum(stmt->user)); - new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid); - AssertState(BoolIsValid(createdb)); - new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb); - new_record[Anum_pg_shadow_usetrace - 1] = BoolGetDatum(false); - AssertState(BoolIsValid(createuser)); - new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser); - /* superuser gets catupd right by default */ - new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser); - - if (password) - { - if (!encrypt_password || isMD5(password)) - new_record[Anum_pg_shadow_passwd - 1] = - DirectFunctionCall1(textin, CStringGetDatum(password)); - else - { - if (!EncryptMD5(password, stmt->user, strlen(stmt->user), - encrypted_password)) - elog(ERROR, "CREATE USER: password encryption failed"); - new_record[Anum_pg_shadow_passwd - 1] = - DirectFunctionCall1(textin, CStringGetDatum(encrypted_password)); - } - } - else - new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n'; - - if (validUntil) - new_record[Anum_pg_shadow_valuntil - 1] = - DirectFunctionCall1(nabstimein, CStringGetDatum(validUntil)); - else - new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n'; - - new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n'; - - tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls); - - /* - * Insert new record in the pg_shadow table - */ - simple_heap_insert(pg_shadow_rel, tuple); - - /* - * Update indexes - */ - if (RelationGetForm(pg_shadow_rel)->relhasindex) - { - Relation idescs[Num_pg_shadow_indices]; - - CatalogOpenIndices(Num_pg_shadow_indices, - Name_pg_shadow_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel, - tuple); - CatalogCloseIndices(Num_pg_shadow_indices, idescs); - } - - /* - * Add the user to the groups specified. We'll just call the below - * AlterGroup for this. - */ - foreach(item, groupElts) - { - AlterGroupStmt ags; - - ags.name = strVal(lfirst(item)); /* the group name to add - * this in */ - ags.action = +1; - ags.listUsers = makeList1(makeInteger(sysid)); - AlterGroup(&ags, "CREATE USER"); - } - - /* - * Now we can clean up; but keep lock until commit. - */ - heap_close(pg_shadow_rel, NoLock); - - /* - * Write the updated pg_shadow and pg_group data to the flat file. - */ - update_pg_pwd_and_pg_group(NULL); -} - - - -/* - * ALTER USER - */ -void -AlterUser(AlterUserStmt *stmt) -{ - Datum new_record[Natts_pg_shadow]; - char new_record_nulls[Natts_pg_shadow]; - char new_record_repl[Natts_pg_shadow]; - Relation pg_shadow_rel; - TupleDesc pg_shadow_dsc; - HeapTuple tuple, - new_tuple; - List *option; - char *password = NULL; /* PostgreSQL user password */ - bool encrypt_password = Password_encryption; /* encrypt password? */ - char encrypted_password[MD5_PASSWD_LEN + 1]; - int createdb = -1; /* Can the user create databases? */ - int createuser = -1; /* Can this user create users? */ - char *validUntil = NULL; /* The time the login is valid - * until */ - DefElem *dpassword = NULL; - DefElem *dcreatedb = NULL; - DefElem *dcreateuser = NULL; - DefElem *dvalidUntil = NULL; - - /* Extract options from the statement node tree */ - foreach(option, stmt->options) - { - DefElem *defel = (DefElem *) lfirst(option); - - if (strcmp(defel->defname, "password") == 0 || - strcmp(defel->defname, "encryptedPassword") == 0 || - strcmp(defel->defname, "unencryptedPassword") == 0) - { - if (dpassword) - elog(ERROR, "ALTER USER: conflicting options"); - dpassword = defel; - if (strcmp(defel->defname, "encryptedPassword") == 0) - encrypt_password = true; - else if (strcmp(defel->defname, "unencryptedPassword") == 0) - encrypt_password = false; - } - else if (strcmp(defel->defname, "createdb") == 0) - { - if (dcreatedb) - elog(ERROR, "ALTER USER: conflicting options"); - dcreatedb = defel; - } - else if (strcmp(defel->defname, "createuser") == 0) - { - if (dcreateuser) - elog(ERROR, "ALTER USER: conflicting options"); - dcreateuser = defel; - } - else if (strcmp(defel->defname, "validUntil") == 0) - { - if (dvalidUntil) - elog(ERROR, "ALTER USER: conflicting options"); - dvalidUntil = defel; - } - else - elog(ERROR, "ALTER USER: option \"%s\" not recognized", - defel->defname); - } - - if (dcreatedb) - createdb = intVal(dcreatedb->arg); - if (dcreateuser) - createuser = intVal(dcreateuser->arg); - if (dvalidUntil) - validUntil = strVal(dvalidUntil->arg); - if (dpassword) - password = strVal(dpassword->arg); - - if (password) - CheckPgUserAclNotNull(); - - /* must be superuser or just want to change your own password */ - if (!superuser() && - !(createdb < 0 && - createuser < 0 && - !validUntil && - password && - strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0)) - elog(ERROR, "ALTER USER: permission denied"); - - /* changes to the flat password file cannot be rolled back */ - if (IsTransactionBlock() && password) - elog(NOTICE, "ALTER USER: password changes cannot be rolled back"); - - /* - * Scan the pg_shadow relation to be certain the user exists. Note we - * secure exclusive lock to protect our update of the flat password - * file. - */ - pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock); - pg_shadow_dsc = RelationGetDescr(pg_shadow_rel); - - tuple = SearchSysCache(SHADOWNAME, - PointerGetDatum(stmt->user), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "ALTER USER: user \"%s\" does not exist", stmt->user); - - /* - * Build an updated tuple, perusing the information just obtained - */ - MemSet(new_record, 0, sizeof(new_record)); - MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); - MemSet(new_record_repl, ' ', sizeof(new_record_repl)); - - new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein, - CStringGetDatum(stmt->user)); - new_record_repl[Anum_pg_shadow_usename - 1] = 'r'; - - /* createdb */ - if (createdb >= 0) - { - new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0); - new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r'; - } - - /* - * createuser (superuser) and catupd - * - * XXX It's rather unclear how to handle catupd. It's probably best to - * keep it equal to the superuser status, otherwise you could end up - * with a situation where no existing superuser can alter the - * catalogs, including pg_shadow! - */ - if (createuser >= 0) - { - new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0); - new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r'; - - new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0); - new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r'; - } - - /* password */ - if (password) - { - if (!encrypt_password || isMD5(password)) - new_record[Anum_pg_shadow_passwd - 1] = - DirectFunctionCall1(textin, CStringGetDatum(password)); - else - { - if (!EncryptMD5(password, stmt->user, strlen(stmt->user), - encrypted_password)) - elog(ERROR, "CREATE USER: password encryption failed"); - new_record[Anum_pg_shadow_passwd - 1] = - DirectFunctionCall1(textin, CStringGetDatum(encrypted_password)); - } - new_record_repl[Anum_pg_shadow_passwd - 1] = 'r'; - } - - /* valid until */ - if (validUntil) - { - new_record[Anum_pg_shadow_valuntil - 1] = - DirectFunctionCall1(nabstimein, CStringGetDatum(validUntil)); - new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r'; - } - - new_tuple = heap_modifytuple(tuple, pg_shadow_rel, new_record, - new_record_nulls, new_record_repl); - simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple); - - /* Update indexes */ - if (RelationGetForm(pg_shadow_rel)->relhasindex) - { - Relation idescs[Num_pg_shadow_indices]; - - CatalogOpenIndices(Num_pg_shadow_indices, - Name_pg_shadow_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel, - new_tuple); - CatalogCloseIndices(Num_pg_shadow_indices, idescs); - } - - ReleaseSysCache(tuple); - heap_freetuple(new_tuple); - - /* - * Now we can clean up. - */ - heap_close(pg_shadow_rel, NoLock); - - /* - * Write the updated pg_shadow and pg_group data to the flat file. - */ - update_pg_pwd_and_pg_group(NULL); -} - - -/* - * ALTER USER ... SET - */ -void -AlterUserSet(AlterUserSetStmt *stmt) -{ - char *valuestr; - HeapTuple oldtuple, - newtuple; - Relation rel; - Datum repl_val[Natts_pg_shadow]; - char repl_null[Natts_pg_shadow]; - char repl_repl[Natts_pg_shadow]; - int i; - - valuestr = flatten_set_variable_args(stmt->variable, stmt->value); - - /* - * RowExclusiveLock is sufficient, because we don't need to update - * the flat password file. - */ - rel = heap_openr(ShadowRelationName, RowExclusiveLock); - oldtuple = SearchSysCache(SHADOWNAME, - PointerGetDatum(stmt->user), - 0, 0, 0); - if (!HeapTupleIsValid(oldtuple)) - elog(ERROR, "user \"%s\" does not exist", stmt->user); - - if (!(superuser() - || ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId())) - elog(ERROR, "permission denied"); - - for (i = 0; i < Natts_pg_shadow; i++) - repl_repl[i] = ' '; - - repl_repl[Anum_pg_shadow_useconfig-1] = 'r'; - if (strcmp(stmt->variable, "all")==0 && valuestr == NULL) - /* RESET ALL */ - repl_null[Anum_pg_shadow_useconfig-1] = 'n'; - else - { - Datum datum; - bool isnull; - ArrayType *array; - - repl_null[Anum_pg_shadow_useconfig-1] = ' '; - - datum = SysCacheGetAttr(SHADOWNAME, oldtuple, - Anum_pg_shadow_useconfig, &isnull); - - array = isnull ? ((ArrayType *) NULL) : DatumGetArrayTypeP(datum); - - if (valuestr) - array = GUCArrayAdd(array, stmt->variable, valuestr); - else - array = GUCArrayDelete(array, stmt->variable); - - repl_val[Anum_pg_shadow_useconfig-1] = PointerGetDatum(array); - } - - newtuple = heap_modifytuple(oldtuple, rel, repl_val, repl_null, repl_repl); - simple_heap_update(rel, &oldtuple->t_self, newtuple); - - { - Relation idescs[Num_pg_shadow_indices]; - - CatalogOpenIndices(Num_pg_shadow_indices, Name_pg_shadow_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_shadow_indices, rel, newtuple); - CatalogCloseIndices(Num_pg_shadow_indices, idescs); - } - - ReleaseSysCache(oldtuple); - heap_close(rel, RowExclusiveLock); -} - - - -/* - * DROP USER - */ -void -DropUser(DropUserStmt *stmt) -{ - Relation pg_shadow_rel; - TupleDesc pg_shadow_dsc; - List *item; - - if (!superuser()) - elog(ERROR, "DROP USER: permission denied"); - - if (IsTransactionBlock()) - elog(NOTICE, "DROP USER cannot be rolled back completely"); - - /* - * Scan the pg_shadow relation to find the usesysid of the user to be - * deleted. Note we secure exclusive lock, because we need to protect - * our update of the flat password file. - */ - pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock); - pg_shadow_dsc = RelationGetDescr(pg_shadow_rel); - - foreach(item, stmt->users) - { - const char *user = strVal(lfirst(item)); - HeapTuple tuple, - tmp_tuple; - Relation pg_rel; - TupleDesc pg_dsc; - ScanKeyData scankey; - HeapScanDesc scan; - int32 usesysid; - - tuple = SearchSysCache(SHADOWNAME, - PointerGetDatum(user), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "DROP USER: user \"%s\" does not exist%s", user, - (length(stmt->users) > 1) ? " (no users removed)" : ""); - - usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid; - - if (usesysid == GetUserId()) - elog(ERROR, "current user cannot be dropped"); - if (usesysid == GetSessionUserId()) - elog(ERROR, "session user cannot be dropped"); - - /* - * Check if user still owns a database. If so, error out. - * - * (It used to be that this function would drop the database - * automatically. This is not only very dangerous for people that - * don't read the manual, it doesn't seem to be the behaviour one - * would expect either.) -- petere 2000/01/14) - */ - pg_rel = heap_openr(DatabaseRelationName, AccessShareLock); - pg_dsc = RelationGetDescr(pg_rel); - - ScanKeyEntryInitialize(&scankey, 0x0, - Anum_pg_database_datdba, F_INT4EQ, - Int32GetDatum(usesysid)); - - scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey); - - if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - char *dbname; - - dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname); - elog(ERROR, "DROP USER: user \"%s\" owns database \"%s\", cannot be removed%s", - user, dbname, - (length(stmt->users) > 1) ? " (no users removed)" : ""); - } - - heap_endscan(scan); - heap_close(pg_rel, AccessShareLock); - - /* - * Somehow we'd have to check for tables, views, etc. owned by the - * user as well, but those could be spread out over all sorts of - * databases which we don't have access to (easily). - */ - - /* - * Remove the user from the pg_shadow table - */ - simple_heap_delete(pg_shadow_rel, &tuple->t_self); - - ReleaseSysCache(tuple); - - /* - * Remove user from groups - * - * try calling alter group drop user for every group - */ - pg_rel = heap_openr(GroupRelationName, ExclusiveLock); - pg_dsc = RelationGetDescr(pg_rel); - scan = heap_beginscan(pg_rel, SnapshotNow, 0, NULL); - while ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - AlterGroupStmt ags; - - /* the group name from which to try to drop the user: */ - ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname)); - ags.action = -1; - ags.listUsers = makeList1(makeInteger(usesysid)); - AlterGroup(&ags, "DROP USER"); - } - heap_endscan(scan); - heap_close(pg_rel, ExclusiveLock); - - /* - * Advance command counter so that later iterations of this loop - * will see the changes already made. This is essential if, for - * example, we are trying to drop two users who are members of the - * same group --- the AlterGroup for the second user had better - * see the tuple updated from the first one. - */ - CommandCounterIncrement(); - } - - /* - * Now we can clean up. - */ - heap_close(pg_shadow_rel, NoLock); - - /* - * Write the updated pg_shadow and pg_group data to the flat file. - */ - update_pg_pwd_and_pg_group(NULL); -} - - - -/* - * CheckPgUserAclNotNull - * - * check to see if there is an ACL on pg_shadow - */ -static void -CheckPgUserAclNotNull(void) -{ - HeapTuple htup; - - htup = SearchSysCache(RELOID, - ObjectIdGetDatum(RelOid_pg_shadow), - 0, 0, 0); - if (!HeapTupleIsValid(htup)) - elog(ERROR, "CheckPgUserAclNotNull: \"%s\" not found", - ShadowRelationName); - - if (heap_attisnull(htup, Anum_pg_class_relacl)) - elog(ERROR, - "To use passwords, you have to revoke permissions on %s " - "so normal users cannot read the passwords. " - "Try 'REVOKE ALL ON \"%s\" FROM PUBLIC'.", - ShadowRelationName, ShadowRelationName); - - ReleaseSysCache(htup); -} - - - -/* - * CREATE GROUP - */ -void -CreateGroup(CreateGroupStmt *stmt) -{ - Relation pg_group_rel; - HeapScanDesc scan; - HeapTuple tuple; - TupleDesc pg_group_dsc; - bool group_exists = false, - sysid_exists = false, - havesysid = false; - int max_id; - Datum new_record[Natts_pg_group]; - char new_record_nulls[Natts_pg_group]; - List *item, - *option, - *newlist = NIL; - IdList *grolist; - int sysid = 0; - List *userElts = NIL; - DefElem *dsysid = NULL; - DefElem *duserElts = NULL; - - foreach(option, stmt->options) - { - DefElem *defel = (DefElem *) lfirst(option); - - if (strcmp(defel->defname, "sysid") == 0) - { - if (dsysid) - elog(ERROR, "CREATE GROUP: conflicting options"); - dsysid = defel; - } - else if (strcmp(defel->defname, "userElts") == 0) - { - if (duserElts) - elog(ERROR, "CREATE GROUP: conflicting options"); - duserElts = defel; - } - else - elog(ERROR, "CREATE GROUP: option \"%s\" not recognized", - defel->defname); - } - - if (dsysid) - { - sysid = intVal(dsysid->arg); - if (sysid <= 0) - elog(ERROR, "group id must be positive"); - havesysid = true; - } - - if (duserElts) - userElts = (List *) duserElts->arg; - - /* - * Make sure the user can do this. - */ - if (!superuser()) - elog(ERROR, "CREATE GROUP: permission denied"); - - if (strcmp(stmt->name, "public") == 0) - elog(ERROR, "CREATE GROUP: group name \"%s\" is reserved", - stmt->name); - - pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock); - pg_group_dsc = RelationGetDescr(pg_group_rel); - - scan = heap_beginscan(pg_group_rel, SnapshotNow, 0, NULL); - max_id = 99; /* start auto-assigned ids at 100 */ - while (!group_exists && !sysid_exists && - (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple); - int32 this_sysid; - - group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0); - - this_sysid = group_form->grosysid; - if (havesysid) /* customized id wanted */ - sysid_exists = (this_sysid == sysid); - else - { - /* pick 1 + max */ - if (this_sysid > max_id) - max_id = this_sysid; - } - } - heap_endscan(scan); - - if (group_exists) - elog(ERROR, "CREATE GROUP: group name \"%s\" already exists", - stmt->name); - if (sysid_exists) - elog(ERROR, "CREATE GROUP: group sysid %d is already assigned", - sysid); - - if (!havesysid) - sysid = max_id + 1; - - /* - * Translate the given user names to ids - */ - foreach(item, userElts) - { - const char *groupuser = strVal(lfirst(item)); - int32 userid = get_usesysid(groupuser); - - if (!intMember(userid, newlist)) - newlist = lappendi(newlist, userid); - } - - /* build an array to insert */ - if (newlist) - grolist = IdListToArray(newlist); - else - grolist = NULL; - - /* - * Form a tuple to insert - */ - new_record[Anum_pg_group_groname - 1] = - DirectFunctionCall1(namein, CStringGetDatum(stmt->name)); - new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid); - new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist); - - new_record_nulls[Anum_pg_group_groname - 1] = ' '; - new_record_nulls[Anum_pg_group_grosysid - 1] = ' '; - new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n'; - - tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls); - - /* - * Insert a new record in the pg_group table - */ - simple_heap_insert(pg_group_rel, tuple); - - /* - * Update indexes - */ - if (RelationGetForm(pg_group_rel)->relhasindex) - { - Relation idescs[Num_pg_group_indices]; - - CatalogOpenIndices(Num_pg_group_indices, - Name_pg_group_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel, - tuple); - CatalogCloseIndices(Num_pg_group_indices, idescs); - } - - heap_close(pg_group_rel, NoLock); - - /* - * Write the updated pg_shadow and pg_group data to the flat file. - */ - update_pg_pwd_and_pg_group(NULL); -} - - - -/* - * ALTER GROUP - */ -void -AlterGroup(AlterGroupStmt *stmt, const char *tag) -{ - Relation pg_group_rel; - TupleDesc pg_group_dsc; - HeapTuple group_tuple; - IdList *oldarray; - Datum datum; - bool null; - List *newlist, - *item; - - /* - * Make sure the user can do this. - */ - if (!superuser()) - elog(ERROR, "%s: permission denied", tag); - - pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock); - pg_group_dsc = RelationGetDescr(pg_group_rel); - - /* - * Fetch existing tuple for group. - */ - group_tuple = SearchSysCache(GRONAME, - PointerGetDatum(stmt->name), - 0, 0, 0); - if (!HeapTupleIsValid(group_tuple)) - elog(ERROR, "%s: group \"%s\" does not exist", tag, stmt->name); - - /* Fetch old group membership. */ - datum = heap_getattr(group_tuple, Anum_pg_group_grolist, - pg_group_dsc, &null); - oldarray = null ? ((IdList *) NULL) : DatumGetIdListP(datum); - - /* initialize list with old array contents */ - newlist = IdArrayToList(oldarray); - - /* - * Now decide what to do. - */ - AssertState(stmt->action == +1 || stmt->action == -1); - - if (stmt->action == +1) /* add users, might also be invoked by - * create user */ - { - /* - * convert the to be added usernames to sysids and add them to - * the list - */ - foreach(item, stmt->listUsers) - { - int32 sysid; - - if (strcmp(tag, "ALTER GROUP") == 0) - { - /* Get the uid of the proposed user to add. */ - sysid = get_usesysid(strVal(lfirst(item))); - } - else if (strcmp(tag, "CREATE USER") == 0) - { - /* - * in this case we already know the uid and it wouldn't be - * in the cache anyway yet - */ - sysid = intVal(lfirst(item)); - } - else - { - elog(ERROR, "AlterGroup: unknown tag %s", tag); - sysid = 0; /* keep compiler quiet */ - } - - if (!intMember(sysid, newlist)) - newlist = lappendi(newlist, sysid); - else - /* - * we silently assume here that this error will only come - * up in a ALTER GROUP statement - */ - elog(WARNING, "%s: user \"%s\" is already in group \"%s\"", - tag, strVal(lfirst(item)), stmt->name); - } - - /* Do the update */ - UpdateGroupMembership(pg_group_rel, group_tuple, newlist); - } /* endif alter group add user */ - - else if (stmt->action == -1) /* drop users from group */ - { - bool is_dropuser = strcmp(tag, "DROP USER") == 0; - - if (newlist == NIL) - { - if (!is_dropuser) - elog(WARNING, "ALTER GROUP: group \"%s\" does not have any members", stmt->name); - } - else - { - /* - * convert the to be dropped usernames to sysids and - * remove them from the list - */ - foreach(item, stmt->listUsers) - { - int32 sysid; - - if (!is_dropuser) - { - /* Get the uid of the proposed user to drop. */ - sysid = get_usesysid(strVal(lfirst(item))); - } - else - { - /* for dropuser we already know the uid */ - sysid = intVal(lfirst(item)); - } - if (intMember(sysid, newlist)) - newlist = lremovei(sysid, newlist); - else if (!is_dropuser) - elog(WARNING, "ALTER GROUP: user \"%s\" is not in group \"%s\"", strVal(lfirst(item)), stmt->name); - } - - /* Do the update */ - UpdateGroupMembership(pg_group_rel, group_tuple, newlist); - } /* endif group not null */ - } /* endif alter group drop user */ - - ReleaseSysCache(group_tuple); - - /* - * Write the updated pg_shadow and pg_group data to the flat files. - */ - heap_close(pg_group_rel, NoLock); - - /* - * Write the updated pg_shadow and pg_group data to the flat file. - */ - update_pg_pwd_and_pg_group(NULL); -} - -/* - * Subroutine for AlterGroup: given a pg_group tuple and a desired new - * membership (expressed as an integer list), form and write an updated tuple. - * The pg_group relation must be open and locked already. - */ -static void -UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple, - List *members) -{ - IdList *newarray; - Datum new_record[Natts_pg_group]; - char new_record_nulls[Natts_pg_group]; - char new_record_repl[Natts_pg_group]; - HeapTuple tuple; - - newarray = IdListToArray(members); - - /* - * Form an updated tuple with the new array and write it back. - */ - MemSet(new_record, 0, sizeof(new_record)); - MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); - MemSet(new_record_repl, ' ', sizeof(new_record_repl)); - - new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray); - new_record_repl[Anum_pg_group_grolist - 1] = 'r'; - - tuple = heap_modifytuple(group_tuple, group_rel, - new_record, new_record_nulls, new_record_repl); - - simple_heap_update(group_rel, &group_tuple->t_self, tuple); - - /* Update indexes */ - if (RelationGetForm(group_rel)->relhasindex) - { - Relation idescs[Num_pg_group_indices]; - - CatalogOpenIndices(Num_pg_group_indices, - Name_pg_group_indices, idescs); - CatalogIndexInsert(idescs, Num_pg_group_indices, group_rel, - tuple); - CatalogCloseIndices(Num_pg_group_indices, idescs); - } -} - - -/* - * Convert an integer list of sysids to an array. - */ -static IdList * -IdListToArray(List *members) -{ - int nmembers = length(members); - IdList *newarray; - List *item; - int i; - - newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32)); - newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32); - newarray->flags = 0; - ARR_NDIM(newarray) = 1; /* one dimensional array */ - ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */ - ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */ - i = 0; - foreach(item, members) - { - ((int *) ARR_DATA_PTR(newarray))[i++] = lfirsti(item); - } - - return newarray; -} - -/* - * Convert an array of sysids to an integer list. - */ -static List * -IdArrayToList(IdList *oldarray) -{ - List *newlist = NIL; - int hibound, - i; - - if (oldarray == NULL) - return NIL; - - Assert(ARR_NDIM(oldarray) == 1); - - hibound = ARR_DIMS(oldarray)[0]; - - for (i = 0; i < hibound; i++) - { - int32 sysid; - - sysid = ((int *) ARR_DATA_PTR(oldarray))[i]; - /* filter out any duplicates --- probably a waste of time */ - if (!intMember(sysid, newlist)) - newlist = lappendi(newlist, sysid); - } - - return newlist; -} - - -/* - * DROP GROUP - */ -void -DropGroup(DropGroupStmt *stmt) -{ - Relation pg_group_rel; - HeapTuple tuple; - - /* - * Make sure the user can do this. - */ - if (!superuser()) - elog(ERROR, "DROP GROUP: permission denied"); - - /* - * Drop the group. - */ - pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock); - - tuple = SearchSysCacheCopy(GRONAME, - PointerGetDatum(stmt->name), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "DROP GROUP: group \"%s\" does not exist", stmt->name); - - simple_heap_delete(pg_group_rel, &tuple->t_self); - - heap_close(pg_group_rel, NoLock); - - /* - * Write the updated pg_shadow and pg_group data to the flat file. - */ - update_pg_pwd_and_pg_group(NULL); -} |