postgresql/src/backend/commands/user.c

1348 lines
35 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* user.c
* Commands for manipulating users and groups.
*
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.151 2005/04/14 20:03:24 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
#include "postgres.h"
1999-07-16 07:00:38 +02:00
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "catalog/pg_database.h"
#include "catalog/pg_group.h"
#include "catalog/pg_shadow.h"
#include "catalog/pg_type.h"
1999-07-16 07:00:38 +02:00
#include "commands/user.h"
#include "libpq/crypt.h"
1999-07-16 07:00:38 +02:00
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/flatfiles.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
extern bool Password_encryption;
1998-12-14 07:50:32 +01:00
static void CheckPgUserAclNotNull(void);
static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
2002-09-04 22:31:48 +02:00
List *members);
static IdList *IdListToArray(List *members);
static List *IdArrayToList(IdList *oldarray);
/*
* CREATE USER
*/
void
CreateUser(CreateUserStmt *stmt)
{
1999-05-25 18:15:34 +02:00
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;
ListCell *item;
ListCell *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)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant 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)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dsysid = defel;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreatedb = defel;
}
else if (strcmp(defel->defname, "createuser") == 0)
{
if (dcreateuser)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreateuser = defel;
}
else if (strcmp(defel->defname, "groupElts") == 0)
{
if (dgroupElts)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dgroupElts = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
else
elog(ERROR, "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)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("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())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create users")));
if (strcmp(stmt->user, "public") == 0)
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("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 eventual update of the flat password file.
*/
pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock);
1998-09-01 05:29:17 +02:00
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 */
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
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
{
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
/* pick 1 + max */
if (this_sysid > max_id)
max_id = this_sysid;
}
}
heap_endscan(scan);
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
if (user_exists)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("user \"%s\" already exists",
stmt->user)));
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
if (sysid_exists)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("user ID %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);
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
{
2001-08-17 04:59:20 +02:00
if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
encrypted_password))
elog(ERROR, "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(abstimein, 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);
/*
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
* Insert new record in the pg_shadow table
*/
simple_heap_insert(pg_shadow_rel, tuple);
/* Update indexes */
CatalogUpdateIndexes(pg_shadow_rel, tuple);
/*
* 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 = list_make1(makeInteger(sysid));
AlterGroup(&ags, "CREATE USER");
}
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
*/
heap_close(pg_shadow_rel, NoLock);
/*
* Set flag to update flat password file at commit.
*/
user_file_update_needed();
}
/*
* 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;
ListCell *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)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant 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)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreatedb = defel;
}
else if (strcmp(defel->defname, "createuser") == 0)
{
if (dcreateuser)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreateuser = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
else
elog(ERROR, "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))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied")));
/*
* 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_open(ShadowRelationId, ExclusiveLock);
1998-09-01 05:29:17 +02:00
pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
tuple = SearchSysCache(SHADOWNAME,
PointerGetDatum(stmt->user),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("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,
2001-03-22 05:01:46 +01:00
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
{
2001-08-17 04:59:20 +02:00
if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
encrypted_password))
elog(ERROR, "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(abstimein, CStringGetDatum(validUntil));
new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
}
new_tuple = heap_modifytuple(tuple, pg_shadow_dsc, new_record,
new_record_nulls, new_record_repl);
simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
/* Update indexes */
CatalogUpdateIndexes(pg_shadow_rel, new_tuple);
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
*/
heap_close(pg_shadow_rel, NoLock);
/*
* Set flag to update flat password file at commit.
*/
user_file_update_needed();
}
/*
* 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);
/*
2002-09-04 22:31:48 +02:00
* RowExclusiveLock is sufficient, because we don't need to update the
* flat password file.
*/
rel = heap_open(ShadowRelationId, RowExclusiveLock);
oldtuple = SearchSysCache(SHADOWNAME,
PointerGetDatum(stmt->user),
0, 0, 0);
if (!HeapTupleIsValid(oldtuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("user \"%s\" does not exist", stmt->user)));
if (!(superuser() ||
2004-08-29 07:07:03 +02:00
((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied")));
for (i = 0; i < Natts_pg_shadow; i++)
repl_repl[i] = ' ';
2002-09-04 22:31:48 +02:00
repl_repl[Anum_pg_shadow_useconfig - 1] = 'r';
if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
{
/* RESET ALL */
2002-09-04 22:31:48 +02:00
repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
}
else
{
2002-09-04 22:31:48 +02:00
Datum datum;
bool isnull;
ArrayType *array;
2002-09-04 22:31:48 +02:00
repl_null[Anum_pg_shadow_useconfig - 1] = ' ';
datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
Anum_pg_shadow_useconfig, &isnull);
array = isnull ? NULL : DatumGetArrayTypeP(datum);
if (valuestr)
array = GUCArrayAdd(array, stmt->variable, valuestr);
else
array = GUCArrayDelete(array, stmt->variable);
if (array)
repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array);
else
repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
}
newtuple = heap_modifytuple(oldtuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
simple_heap_update(rel, &oldtuple->t_self, newtuple);
CatalogUpdateIndexes(rel, newtuple);
ReleaseSysCache(oldtuple);
heap_close(rel, RowExclusiveLock);
}
/*
* DROP USER
*/
void
DropUser(DropUserStmt *stmt)
{
Relation pg_shadow_rel;
TupleDesc pg_shadow_dsc;
ListCell *item;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to drop users")));
/*
* 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_open(ShadowRelationId, 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;
AclId usesysid;
tuple = SearchSysCache(SHADOWNAME,
PointerGetDatum(user),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("user \"%s\" does not exist", user)));
usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
if (usesysid == GetUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("current user cannot be dropped")));
if (usesysid == GetSessionUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("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_open(DatabaseRelationId, AccessShareLock);
pg_dsc = RelationGetDescr(pg_rel);
ScanKeyInit(&scankey,
Anum_pg_database_datdba,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(usesysid));
scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
char *dbname;
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("user \"%s\" cannot be dropped", user),
2003-08-04 02:43:34 +02:00
errdetail("The user owns database \"%s\".", dbname)));
}
heap_endscan(scan);
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
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_open(GroupRelationId, 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 = list_make1(makeInteger(usesysid));
AlterGroup(&ags, "DROP USER");
}
heap_endscan(scan);
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
heap_close(pg_rel, ExclusiveLock);
2001-03-22 05:01:46 +01:00
/*
* 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; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
*/
heap_close(pg_shadow_rel, NoLock);
/*
* Set flag to update flat password file at commit.
*/
user_file_update_needed();
}
2003-06-27 16:45:32 +02:00
/*
* Rename user
*/
void
RenameUser(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
newtuple;
TupleDesc dsc;
2003-06-27 16:45:32 +02:00
Relation rel;
Datum datum;
bool isnull;
Datum repl_val[Natts_pg_shadow];
char repl_null[Natts_pg_shadow];
char repl_repl[Natts_pg_shadow];
int i;
2004-08-29 07:07:03 +02:00
2003-06-27 16:45:32 +02:00
/* ExclusiveLock because we need to update the password file */
rel = heap_open(ShadowRelationId, ExclusiveLock);
dsc = RelationGetDescr(rel);
2003-06-27 16:45:32 +02:00
oldtuple = SearchSysCache(SHADOWNAME,
2004-08-29 07:07:03 +02:00
CStringGetDatum(oldname),
0, 0, 0);
if (!HeapTupleIsValid(oldtuple))
2003-06-27 16:45:32 +02:00
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
2003-06-27 16:45:32 +02:00
errmsg("user \"%s\" does not exist", oldname)));
/*
2003-08-04 02:43:34 +02:00
* XXX Client applications probably store the session user somewhere,
* so renaming it could cause confusion. On the other hand, there may
* not be an actual problem besides a little confusion, so think about
* this and decide.
2003-06-27 16:45:32 +02:00
*/
if (((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetSessionUserId())
2003-06-27 16:45:32 +02:00
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2003-06-27 16:45:32 +02:00
errmsg("session user may not be renamed")));
/* make sure the new name doesn't exist */
if (SearchSysCacheExists(SHADOWNAME,
CStringGetDatum(newname),
0, 0, 0))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
2003-06-27 16:45:32 +02:00
errmsg("user \"%s\" already exists", newname)));
/* must be superuser */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to rename users")));
2003-06-27 16:45:32 +02:00
for (i = 0; i < Natts_pg_shadow; i++)
repl_repl[i] = ' ';
repl_repl[Anum_pg_shadow_usename - 1] = 'r';
repl_val[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
2004-08-29 07:07:03 +02:00
CStringGetDatum(newname));
repl_null[Anum_pg_shadow_usename - 1] = ' ';
datum = heap_getattr(oldtuple, Anum_pg_shadow_passwd, dsc, &isnull);
if (!isnull && isMD5(DatumGetCString(DirectFunctionCall1(textout, datum))))
{
/* MD5 uses the username as salt, so just clear it on a rename */
repl_repl[Anum_pg_shadow_passwd - 1] = 'r';
repl_null[Anum_pg_shadow_passwd - 1] = 'n';
2004-08-29 07:07:03 +02:00
ereport(NOTICE,
2004-08-29 07:07:03 +02:00
(errmsg("MD5 password cleared because of user rename")));
}
2004-08-29 07:07:03 +02:00
newtuple = heap_modifytuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
simple_heap_update(rel, &oldtuple->t_self, newtuple);
2004-08-29 07:07:03 +02:00
CatalogUpdateIndexes(rel, newtuple);
2003-06-27 16:45:32 +02:00
ReleaseSysCache(oldtuple);
2003-06-27 16:45:32 +02:00
heap_close(rel, NoLock);
user_file_update_needed();
2003-06-27 16:45:32 +02:00
}
/*
* CheckPgUserAclNotNull
*
* check to see if there is an ACL on pg_shadow
*/
static void
CheckPgUserAclNotNull(void)
{
HeapTuple htup;
htup = SearchSysCache(RELOID,
ObjectIdGetDatum(ShadowRelationId),
0, 0, 0);
2003-08-04 02:43:34 +02:00
if (!HeapTupleIsValid(htup)) /* should not happen, we hope */
elog(ERROR, "cache lookup failed for relation %u", ShadowRelationId);
if (heap_attisnull(htup, Anum_pg_class_relacl))
{
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(htup);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("before using passwords you must revoke privileges on %s",
NameStr(classForm->relname)),
errdetail("This restriction is to prevent unprivileged users from reading the passwords."),
errhint("Try REVOKE ALL ON \"%s\" FROM PUBLIC.",
NameStr(classForm->relname))));
}
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];
ListCell *item;
ListCell *option;
List *newlist = NIL;
2002-04-28 02:36:38 +02:00
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)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dsysid = defel;
}
else if (strcmp(defel->defname, "userElts") == 0)
{
if (duserElts)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
duserElts = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dsysid)
{
sysid = intVal(dsysid->arg);
if (sysid <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("group ID must be positive")));
havesysid = true;
}
if (duserElts)
userElts = (List *) duserElts->arg;
/*
* Make sure the user can do this.
*/
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create groups")));
if (strcmp(stmt->name, "public") == 0)
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("group name \"%s\" is reserved",
stmt->name)));
/*
* Scan the pg_group relation to be certain the group or id doesn't
* already exist. Note we secure exclusive lock, because we also need
* to be sure of what the next grosysid should be, and we need to
* protect our eventual update of the flat group file.
*/
pg_group_rel = heap_open(GroupRelationId, 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 */
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
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
{
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
/* pick 1 + max */
if (this_sysid > max_id)
max_id = this_sysid;
}
}
heap_endscan(scan);
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
if (group_exists)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("group \"%s\" already exists",
stmt->name)));
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
if (sysid_exists)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("group ID %d is already assigned", sysid)));
/* If no sysid given, use max existing id + 1 */
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 (!list_member_int(newlist, userid))
newlist = lappend_int(newlist, userid);
}
/* build an array to insert */
if (newlist)
2002-04-28 02:36:38 +02:00
grolist = IdListToArray(newlist);
else
2002-04-28 02:36:38 +02:00
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);
2002-04-28 02:36:38 +02:00
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] = ' ';
2002-04-28 02:36:38 +02:00
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 */
CatalogUpdateIndexes(pg_group_rel, tuple);
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
*/
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
heap_close(pg_group_rel, NoLock);
/*
* Set flag to update flat group file at commit.
*/
group_file_update_needed();
}
/*
* 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;
ListCell *item;
/*
* Make sure the user can do this.
*/
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter groups")));
/*
* Secure exclusive lock to protect our update of the flat group file.
*/
pg_group_rel = heap_open(GroupRelationId, 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))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("group \"%s\" does not exist", stmt->name)));
/* Fetch old group membership. */
datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
pg_group_dsc, &null);
oldarray = null ? 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 */
{
/*
2002-09-04 22:31:48 +02:00
* convert the to be added usernames to sysids and add them to the
* list
*/
foreach(item, stmt->listUsers)
{
2002-09-04 22:31:48 +02:00
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, "unexpected tag: \"%s\"", tag);
sysid = 0; /* keep compiler quiet */
}
if (!list_member_int(newlist, sysid))
newlist = lappend_int(newlist, sysid);
}
/* 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)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("group \"%s\" does not have any members",
stmt->name)));
}
else
{
/*
2002-09-04 22:31:48 +02:00
* 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 (list_member_int(newlist, sysid))
newlist = list_delete_int(newlist, sysid);
else if (!is_dropuser)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("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);
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
*/
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
heap_close(pg_group_rel, NoLock);
/*
* Set flag to update flat group file at commit.
*/
group_file_update_needed();
}
/*
* 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, RelationGetDescr(group_rel),
2002-09-04 22:31:48 +02:00
new_record, new_record_nulls, new_record_repl);
simple_heap_update(group_rel, &group_tuple->t_self, tuple);
/* Update indexes */
CatalogUpdateIndexes(group_rel, tuple);
}
/*
* Convert an integer list of sysids to an array.
*/
static IdList *
IdListToArray(List *members)
{
int nmembers = list_length(members);
IdList *newarray;
ListCell *item;
int i;
newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
newarray->flags = 0;
newarray->elemtype = INT4OID;
ARR_NDIM(newarray) = 1; /* one dimensional array */
ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */
2002-09-04 22:31:48 +02:00
ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */
i = 0;
foreach(item, members)
((int *) ARR_DATA_PTR(newarray))[i++] = lfirst_int(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);
Assert(ARR_ELEMTYPE(oldarray) == INT4OID);
hibound = ARR_DIMS(oldarray)[0];
for (i = 0; i < hibound; i++)
{
int32 sysid;
sysid = ((int32 *) ARR_DATA_PTR(oldarray))[i];
/* filter out any duplicates --- probably a waste of time */
if (!list_member_int(newlist, sysid))
newlist = lappend_int(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())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to drop groups")));
/*
* Secure exclusive lock to protect our update of the flat group file.
*/
pg_group_rel = heap_open(GroupRelationId, ExclusiveLock);
/* Find and delete the group. */
tuple = SearchSysCacheCopy(GRONAME,
PointerGetDatum(stmt->name),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("group \"%s\" does not exist", stmt->name)));
simple_heap_delete(pg_group_rel, &tuple->t_self);
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
*/
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
heap_close(pg_group_rel, NoLock);
/*
* Set flag to update flat group file at commit.
*/
group_file_update_needed();
}
2003-06-27 16:45:32 +02:00
/*
* Rename group
*/
void
RenameGroup(const char *oldname, const char *newname)
{
HeapTuple tup;
Relation rel;
/* ExclusiveLock because we need to update the flat group file */
rel = heap_open(GroupRelationId, ExclusiveLock);
2003-06-27 16:45:32 +02:00
tup = SearchSysCacheCopy(GRONAME,
CStringGetDatum(oldname),
0, 0, 0);
if (!HeapTupleIsValid(tup))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
2003-06-27 16:45:32 +02:00
errmsg("group \"%s\" does not exist", oldname)));
/* make sure the new name doesn't exist */
if (SearchSysCacheExists(GRONAME,
CStringGetDatum(newname),
0, 0, 0))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("group \"%s\" already exists", newname)));
2003-06-27 16:45:32 +02:00
/* must be superuser */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to rename groups")));
2003-06-27 16:45:32 +02:00
/* rename */
namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
simple_heap_update(rel, &tup->t_self, tup);
CatalogUpdateIndexes(rel, tup);
heap_close(rel, NoLock);
heap_freetuple(tup);
group_file_update_needed();
2003-06-27 16:45:32 +02:00
}