mirror of
https://github.com/zebrajr/postgres.git
synced 2025-12-07 00:20:24 +01:00
maintained for each cache entry. A cache entry will not be freed until the matching ReleaseSysCache call has been executed. This eliminates worries about cache entries getting dropped while still in use. See my posting to pg-hackers of even date for more info.
1112 lines
31 KiB
C
1112 lines
31 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* user.c
|
|
* Commands for manipulating users and groups.
|
|
*
|
|
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.70 2000/11/16 22:30:19 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "postgres.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 "utils/array.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/syscache.h"
|
|
|
|
static void CheckPgUserAclNotNull(void);
|
|
|
|
#define SQL_LENGTH 512
|
|
|
|
/*---------------------------------------------------------------------
|
|
* write_password_file / update_pg_pwd
|
|
*
|
|
* copy the modified contents of pg_shadow to a file used by the postmaster
|
|
* for user authentication. The file is stored as $PGDATA/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.
|
|
*---------------------------------------------------------------------
|
|
*/
|
|
static void
|
|
write_password_file(Relation rel)
|
|
{
|
|
char *filename,
|
|
*tempname;
|
|
int bufsize;
|
|
FILE *fp;
|
|
int flagfd;
|
|
mode_t oumask;
|
|
HeapScanDesc scan;
|
|
HeapTuple tuple;
|
|
TupleDesc dsc = RelationGetDescr(rel);
|
|
|
|
/*
|
|
* 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 = crypt_getpwdfilename();
|
|
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(rel, false, SnapshotSelf, 0, NULL);
|
|
while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
|
{
|
|
Datum datum_n,
|
|
datum_p,
|
|
datum_v;
|
|
bool null_n,
|
|
null_p,
|
|
null_v;
|
|
|
|
datum_n = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &null_n);
|
|
if (null_n)
|
|
continue; /* don't allow empty users */
|
|
datum_p = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &null_p);
|
|
|
|
/*
|
|
* It could be argued that people having a null password shouldn't
|
|
* be allowed to connect, because they need to have a password set
|
|
* up first. If you think assuming an empty password in that case
|
|
* is better, erase the following line.
|
|
*/
|
|
if (null_p)
|
|
continue;
|
|
datum_v = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &null_v);
|
|
|
|
/*
|
|
* These fake entries are not really necessary. To remove them,
|
|
* the parser in backend/libpq/crypt.c would need to be adjusted.
|
|
* Initdb might also need adjustments.
|
|
*/
|
|
fprintf(fp,
|
|
"%s"
|
|
CRYPT_PWD_FILE_SEPSTR
|
|
"0"
|
|
CRYPT_PWD_FILE_SEPSTR
|
|
"x"
|
|
CRYPT_PWD_FILE_SEPSTR
|
|
"x"
|
|
CRYPT_PWD_FILE_SEPSTR
|
|
"x"
|
|
CRYPT_PWD_FILE_SEPSTR
|
|
"x"
|
|
CRYPT_PWD_FILE_SEPSTR
|
|
"%s"
|
|
CRYPT_PWD_FILE_SEPSTR
|
|
"%s\n",
|
|
DatumGetCString(DirectFunctionCall1(nameout,
|
|
NameGetDatum(DatumGetName(datum_n)))),
|
|
null_p ? "" :
|
|
DatumGetCString(DirectFunctionCall1(textout, datum_p)),
|
|
null_v ? "\\N" :
|
|
DatumGetCString(DirectFunctionCall1(nabstimeout, datum_v))
|
|
);
|
|
}
|
|
heap_endscan(scan);
|
|
|
|
fflush(fp);
|
|
if (ferror(fp))
|
|
elog(ERROR, "%s: %m", tempname);
|
|
FreeFile(fp);
|
|
|
|
/*
|
|
* And rename the temp file to its final name, deleting the old
|
|
* pg_pwd.
|
|
*/
|
|
if (rename(tempname, filename))
|
|
elog(ERROR, "rename %s to %s: %m", tempname, filename);
|
|
|
|
pfree((void *) tempname);
|
|
pfree((void *) filename);
|
|
|
|
/*
|
|
* Create a flag file the postmaster will detect the next time it
|
|
* tries to authenticate a user. The postmaster will know to reload
|
|
* the pg_pwd file contents. Note: we used to elog(ERROR) if the file
|
|
* creation failed, but it's a little silly to abort the transaction
|
|
* at this point, so let's just make it a NOTICE.
|
|
*/
|
|
filename = crypt_getpwdreloadfilename();
|
|
flagfd = BasicOpenFile(filename, O_WRONLY | O_CREAT, 0600);
|
|
if (flagfd < 0)
|
|
elog(NOTICE, "write_password_file: unable to write %s: %m", filename);
|
|
else
|
|
close(flagfd);
|
|
pfree((void *) filename);
|
|
}
|
|
|
|
|
|
|
|
/* This is the wrapper for triggers. */
|
|
Datum
|
|
update_pg_pwd(PG_FUNCTION_ARGS)
|
|
{
|
|
Relation rel = heap_openr(ShadowRelationName, AccessExclusiveLock);
|
|
|
|
write_password_file(rel);
|
|
heap_close(rel, AccessExclusiveLock);
|
|
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;
|
|
int max_id = -1;
|
|
List *item;
|
|
|
|
havesysid = stmt->sysid > 0;
|
|
|
|
/* Check some permissions first */
|
|
if (stmt->password)
|
|
CheckPgUserAclNotNull();
|
|
|
|
if (!superuser())
|
|
elog(ERROR, "CREATE USER: permission denied");
|
|
|
|
/*
|
|
* 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, AccessExclusiveLock);
|
|
pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
|
|
|
|
scan = heap_beginscan(pg_shadow_rel, false, SnapshotNow, 0, NULL);
|
|
while (!user_exists && !sysid_exists && HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
|
{
|
|
Datum datum;
|
|
bool null;
|
|
|
|
datum = heap_getattr(tuple, Anum_pg_shadow_usename, pg_shadow_dsc, &null);
|
|
user_exists = datum && !null && (strcmp((char *) datum, stmt->user) == 0);
|
|
|
|
datum = heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null);
|
|
if (havesysid) /* customized id wanted */
|
|
sysid_exists = datum && !null && ((int) datum == stmt->sysid);
|
|
else
|
|
/* pick 1 + max */
|
|
{
|
|
if ((int) datum > max_id)
|
|
max_id = (int) datum;
|
|
}
|
|
}
|
|
heap_endscan(scan);
|
|
|
|
if (user_exists || sysid_exists)
|
|
{
|
|
heap_close(pg_shadow_rel, AccessExclusiveLock);
|
|
if (user_exists)
|
|
elog(ERROR, "CREATE USER: user name \"%s\" already exists", stmt->user);
|
|
else
|
|
elog(ERROR, "CREATE USER: sysid %d is already assigned", stmt->sysid);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Build a tuple to insert
|
|
*/
|
|
new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
|
|
CStringGetDatum(stmt->user));
|
|
new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(havesysid ? stmt->sysid : max_id + 1);
|
|
|
|
AssertState(BoolIsValid(stmt->createdb));
|
|
new_record[Anum_pg_shadow_usecreatedb - 1] = (Datum) (stmt->createdb);
|
|
new_record[Anum_pg_shadow_usetrace - 1] = (Datum) (false);
|
|
AssertState(BoolIsValid(stmt->createuser));
|
|
new_record[Anum_pg_shadow_usesuper - 1] = (Datum) (stmt->createuser);
|
|
/* superuser gets catupd right by default */
|
|
new_record[Anum_pg_shadow_usecatupd - 1] = (Datum) (stmt->createuser);
|
|
|
|
if (stmt->password)
|
|
new_record[Anum_pg_shadow_passwd - 1] =
|
|
DirectFunctionCall1(textin, CStringGetDatum(stmt->password));
|
|
if (stmt->validUntil)
|
|
new_record[Anum_pg_shadow_valuntil - 1] =
|
|
DirectFunctionCall1(nabstimein, CStringGetDatum(stmt->validUntil));
|
|
|
|
new_record_nulls[Anum_pg_shadow_usename - 1] = ' ';
|
|
new_record_nulls[Anum_pg_shadow_usesysid - 1] = ' ';
|
|
|
|
new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = ' ';
|
|
new_record_nulls[Anum_pg_shadow_usetrace - 1] = ' ';
|
|
new_record_nulls[Anum_pg_shadow_usesuper - 1] = ' ';
|
|
new_record_nulls[Anum_pg_shadow_usecatupd - 1] = ' ';
|
|
|
|
new_record_nulls[Anum_pg_shadow_passwd - 1] = stmt->password ? ' ' : 'n';
|
|
new_record_nulls[Anum_pg_shadow_valuntil - 1] = stmt->validUntil ? ' ' : 'n';
|
|
|
|
tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
|
|
Assert(tuple);
|
|
|
|
/*
|
|
* Insert a new record in the pg_shadow table
|
|
*/
|
|
if (heap_insert(pg_shadow_rel, tuple) == InvalidOid)
|
|
elog(ERROR, "CREATE USER: heap_insert failed");
|
|
|
|
/*
|
|
* 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, stmt->groupElts)
|
|
{
|
|
AlterGroupStmt ags;
|
|
|
|
ags.name = strVal(lfirst(item)); /* the group name to add
|
|
* this in */
|
|
ags.action = +1;
|
|
ags.listUsers = lcons((void *) makeInteger(havesysid ? stmt->sysid : max_id + 1), NIL);
|
|
AlterGroup(&ags, "CREATE USER");
|
|
}
|
|
|
|
/*
|
|
* Write the updated pg_shadow data to the flat password file.
|
|
*/
|
|
write_password_file(pg_shadow_rel);
|
|
|
|
/*
|
|
* Now we can clean up.
|
|
*/
|
|
heap_close(pg_shadow_rel, AccessExclusiveLock);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* ALTER USER
|
|
*/
|
|
extern void
|
|
AlterUser(AlterUserStmt *stmt)
|
|
{
|
|
Datum new_record[Natts_pg_shadow];
|
|
char new_record_nulls[Natts_pg_shadow];
|
|
Relation pg_shadow_rel;
|
|
TupleDesc pg_shadow_dsc;
|
|
HeapTuple tuple,
|
|
new_tuple;
|
|
bool null;
|
|
|
|
if (stmt->password)
|
|
CheckPgUserAclNotNull();
|
|
|
|
/* must be superuser or just want to change your own password */
|
|
if (!superuser() &&
|
|
!(stmt->createdb == 0 && stmt->createuser == 0 && !stmt->validUntil
|
|
&& stmt->password && strcmp(GetUserName(GetUserId()), stmt->user) == 0))
|
|
elog(ERROR, "ALTER USER: permission denied");
|
|
|
|
/* changes to the flat password file cannot be rolled back */
|
|
if (IsTransactionBlock() && stmt->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, AccessExclusiveLock);
|
|
pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
|
|
|
|
tuple = SearchSysCache(SHADOWNAME,
|
|
PointerGetDatum(stmt->user),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tuple))
|
|
{
|
|
heap_close(pg_shadow_rel, AccessExclusiveLock);
|
|
elog(ERROR, "ALTER USER: user \"%s\" does not exist", stmt->user);
|
|
}
|
|
|
|
/*
|
|
* Build a tuple to update, perusing the information just obtained
|
|
*/
|
|
new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
|
|
CStringGetDatum(stmt->user));
|
|
new_record_nulls[Anum_pg_shadow_usename - 1] = ' ';
|
|
|
|
/* sysid - leave as is */
|
|
new_record[Anum_pg_shadow_usesysid - 1] = heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null);
|
|
new_record_nulls[Anum_pg_shadow_usesysid - 1] = null ? 'n' : ' ';
|
|
|
|
/* createdb */
|
|
if (stmt->createdb == 0)
|
|
{
|
|
/* don't change */
|
|
new_record[Anum_pg_shadow_usecreatedb - 1] = heap_getattr(tuple, Anum_pg_shadow_usecreatedb, pg_shadow_dsc, &null);
|
|
new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = null ? 'n' : ' ';
|
|
}
|
|
else
|
|
{
|
|
new_record[Anum_pg_shadow_usecreatedb - 1] = (Datum) (stmt->createdb > 0 ? true : false);
|
|
new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = ' ';
|
|
}
|
|
|
|
/* trace - leave as is */
|
|
new_record[Anum_pg_shadow_usetrace - 1] = heap_getattr(tuple, Anum_pg_shadow_usetrace, pg_shadow_dsc, &null);
|
|
new_record_nulls[Anum_pg_shadow_usetrace - 1] = null ? 'n' : ' ';
|
|
|
|
/* createuser (superuser) */
|
|
if (stmt->createuser == 0)
|
|
{
|
|
/* don't change */
|
|
new_record[Anum_pg_shadow_usesuper - 1] = heap_getattr(tuple, Anum_pg_shadow_usesuper, pg_shadow_dsc, &null);
|
|
new_record_nulls[Anum_pg_shadow_usesuper - 1] = null ? 'n' : ' ';
|
|
}
|
|
else
|
|
{
|
|
new_record[Anum_pg_shadow_usesuper - 1] = (Datum) (stmt->createuser > 0 ? true : false);
|
|
new_record_nulls[Anum_pg_shadow_usesuper - 1] = ' ';
|
|
}
|
|
|
|
/* catupd - set to false if someone's superuser priv is being yanked */
|
|
if (stmt->createuser < 0)
|
|
{
|
|
new_record[Anum_pg_shadow_usecatupd - 1] = (Datum) (false);
|
|
new_record_nulls[Anum_pg_shadow_usecatupd - 1] = ' ';
|
|
}
|
|
else
|
|
{
|
|
/* leave alone */
|
|
new_record[Anum_pg_shadow_usecatupd - 1] = heap_getattr(tuple, Anum_pg_shadow_usecatupd, pg_shadow_dsc, &null);
|
|
new_record_nulls[Anum_pg_shadow_usecatupd - 1] = null ? 'n' : ' ';
|
|
}
|
|
|
|
/* password */
|
|
if (stmt->password)
|
|
{
|
|
new_record[Anum_pg_shadow_passwd - 1] =
|
|
DirectFunctionCall1(textin, CStringGetDatum(stmt->password));
|
|
new_record_nulls[Anum_pg_shadow_passwd - 1] = ' ';
|
|
}
|
|
else
|
|
{
|
|
/* leave as is */
|
|
new_record[Anum_pg_shadow_passwd - 1] =
|
|
heap_getattr(tuple, Anum_pg_shadow_passwd, pg_shadow_dsc, &null);
|
|
new_record_nulls[Anum_pg_shadow_passwd - 1] = null ? 'n' : ' ';
|
|
}
|
|
|
|
/* valid until */
|
|
if (stmt->validUntil)
|
|
{
|
|
new_record[Anum_pg_shadow_valuntil - 1] =
|
|
DirectFunctionCall1(nabstimein, CStringGetDatum(stmt->validUntil));
|
|
new_record_nulls[Anum_pg_shadow_valuntil - 1] = ' ';
|
|
}
|
|
else
|
|
{
|
|
/* leave as is */
|
|
new_record[Anum_pg_shadow_valuntil - 1] =
|
|
heap_getattr(tuple, Anum_pg_shadow_valuntil, pg_shadow_dsc, &null);
|
|
new_record_nulls[Anum_pg_shadow_valuntil - 1] = null ? 'n' : ' ';
|
|
}
|
|
|
|
new_tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
|
|
Assert(new_tuple);
|
|
/* XXX check return value of this? */
|
|
heap_update(pg_shadow_rel, &tuple->t_self, new_tuple, NULL);
|
|
|
|
|
|
/* 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);
|
|
|
|
/*
|
|
* Write the updated pg_shadow data to the flat password file.
|
|
*/
|
|
write_password_file(pg_shadow_rel);
|
|
|
|
/*
|
|
* Now we can clean up.
|
|
*/
|
|
heap_close(pg_shadow_rel, AccessExclusiveLock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* 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, AccessExclusiveLock);
|
|
pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
|
|
|
|
foreach(item, stmt->users)
|
|
{
|
|
HeapTuple tuple,
|
|
tmp_tuple;
|
|
Relation pg_rel;
|
|
TupleDesc pg_dsc;
|
|
ScanKeyData scankey;
|
|
HeapScanDesc scan;
|
|
Datum datum;
|
|
bool null;
|
|
int32 usesysid;
|
|
const char *user = strVal(lfirst(item));
|
|
|
|
tuple = SearchSysCache(SHADOWNAME,
|
|
PointerGetDatum(user),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tuple))
|
|
{
|
|
heap_close(pg_shadow_rel, AccessExclusiveLock);
|
|
elog(ERROR, "DROP USER: user \"%s\" does not exist%s", user,
|
|
(length(stmt->users) > 1) ? " (no users removed)" : "");
|
|
}
|
|
|
|
usesysid = DatumGetInt32(heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null));
|
|
|
|
/*-------------------
|
|
* 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, AccessExclusiveLock);
|
|
pg_dsc = RelationGetDescr(pg_rel);
|
|
|
|
ScanKeyEntryInitialize(&scankey, 0x0, Anum_pg_database_datdba, F_INT4EQ,
|
|
Int32GetDatum(usesysid));
|
|
|
|
scan = heap_beginscan(pg_rel, false, SnapshotNow, 1, &scankey);
|
|
|
|
if (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
|
|
{
|
|
datum = heap_getattr(tmp_tuple, Anum_pg_database_datname, pg_dsc, &null);
|
|
heap_close(pg_shadow_rel, AccessExclusiveLock);
|
|
elog(ERROR, "DROP USER: user \"%s\" owns database \"%s\", cannot be removed%s",
|
|
user,
|
|
DatumGetCString(DirectFunctionCall1(nameout,
|
|
NameGetDatum(DatumGetName(datum)))),
|
|
(length(stmt->users) > 1) ? " (no users removed)" : ""
|
|
);
|
|
}
|
|
|
|
heap_endscan(scan);
|
|
heap_close(pg_rel, AccessExclusiveLock);
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
heap_delete(pg_shadow_rel, &tuple->t_self, NULL);
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
/*
|
|
* Remove user from groups
|
|
*
|
|
* try calling alter group drop user for every group
|
|
*/
|
|
pg_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
|
|
pg_dsc = RelationGetDescr(pg_rel);
|
|
scan = heap_beginscan(pg_rel, false, SnapshotNow, 0, NULL);
|
|
while (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
|
|
{
|
|
AlterGroupStmt ags;
|
|
|
|
/* the group name from which to try to drop the user: */
|
|
datum = heap_getattr(tmp_tuple, Anum_pg_group_groname, pg_dsc, &null);
|
|
ags.name = DatumGetCString(DirectFunctionCall1(nameout, datum));
|
|
ags.action = -1;
|
|
ags.listUsers = lcons((void *) makeInteger(usesysid), NIL);
|
|
AlterGroup(&ags, "DROP USER");
|
|
}
|
|
heap_endscan(scan);
|
|
heap_close(pg_rel, AccessExclusiveLock);
|
|
/*
|
|
* 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();
|
|
}
|
|
|
|
/*
|
|
* Write the updated pg_shadow data to the flat password file.
|
|
*/
|
|
write_password_file(pg_shadow_rel);
|
|
|
|
/*
|
|
* Now we can clean up.
|
|
*/
|
|
heap_close(pg_shadow_rel, AccessExclusiveLock);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* CheckPgUserAclNotNull
|
|
*
|
|
* check to see if there is an ACL on pg_shadow
|
|
*/
|
|
static void
|
|
CheckPgUserAclNotNull()
|
|
{
|
|
HeapTuple htup;
|
|
|
|
htup = SearchSysCache(RELNAME,
|
|
PointerGetDatum(ShadowRelationName),
|
|
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;
|
|
int max_id = 0;
|
|
Datum new_record[Natts_pg_group];
|
|
char new_record_nulls[Natts_pg_group];
|
|
List *item,
|
|
*newlist = NULL;
|
|
ArrayType *userarray;
|
|
|
|
/*
|
|
* Make sure the user can do this.
|
|
*/
|
|
if (!superuser())
|
|
elog(ERROR, "CREATE GROUP: permission denied");
|
|
|
|
pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
|
|
pg_group_dsc = RelationGetDescr(pg_group_rel);
|
|
|
|
scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
|
|
while (!group_exists && !sysid_exists && HeapTupleIsValid(tuple = heap_getnext(scan, false)))
|
|
{
|
|
Datum datum;
|
|
bool null;
|
|
|
|
datum = heap_getattr(tuple, Anum_pg_group_groname, pg_group_dsc, &null);
|
|
group_exists = datum && !null && (strcmp((char *) datum, stmt->name) == 0);
|
|
|
|
datum = heap_getattr(tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
|
|
if (stmt->sysid >= 0) /* customized id wanted */
|
|
sysid_exists = datum && !null && ((int) datum == stmt->sysid);
|
|
else
|
|
/* pick 1 + max */
|
|
{
|
|
if ((int) datum > max_id)
|
|
max_id = (int) datum;
|
|
}
|
|
}
|
|
heap_endscan(scan);
|
|
|
|
if (group_exists || sysid_exists)
|
|
{
|
|
heap_close(pg_group_rel, AccessExclusiveLock);
|
|
if (group_exists)
|
|
elog(ERROR, "CREATE GROUP: group name \"%s\" already exists", stmt->name);
|
|
else
|
|
elog(ERROR, "CREATE GROUP: group sysid %d is already assigned", stmt->sysid);
|
|
}
|
|
|
|
/*
|
|
* Translate the given user names to ids
|
|
*/
|
|
foreach(item, stmt->initUsers)
|
|
{
|
|
const char *groupuser = strVal(lfirst(item));
|
|
Value *v;
|
|
|
|
tuple = SearchSysCache(SHADOWNAME,
|
|
PointerGetDatum(groupuser),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "CREATE GROUP: user \"%s\" does not exist", groupuser);
|
|
|
|
v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
|
|
if (!member(v, newlist))
|
|
newlist = lcons(v, newlist);
|
|
ReleaseSysCache(tuple);
|
|
}
|
|
|
|
/* build an array to insert */
|
|
if (newlist)
|
|
{
|
|
int i;
|
|
|
|
userarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
|
|
userarray->size = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
|
|
userarray->flags = 0;
|
|
ARR_NDIM(userarray) = 1;/* one dimensional array */
|
|
ARR_LBOUND(userarray)[0] = 1; /* axis starts at one */
|
|
ARR_DIMS(userarray)[0] = length(newlist); /* axis is this long */
|
|
/* fill the array */
|
|
i = 0;
|
|
foreach(item, newlist)
|
|
((int *) ARR_DATA_PTR(userarray))[i++] = intVal(lfirst(item));
|
|
}
|
|
else
|
|
userarray = NULL;
|
|
|
|
/*
|
|
* Form a tuple to insert
|
|
*/
|
|
if (stmt->sysid >= 0)
|
|
max_id = stmt->sysid;
|
|
else
|
|
max_id++;
|
|
|
|
new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
|
|
new_record[Anum_pg_group_grosysid - 1] = (Datum) (max_id);
|
|
new_record[Anum_pg_group_grolist - 1] = (Datum) userarray;
|
|
|
|
new_record_nulls[Anum_pg_group_groname - 1] = ' ';
|
|
new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
|
|
new_record_nulls[Anum_pg_group_grolist - 1] = userarray ? ' ' : 'n';
|
|
|
|
tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
|
|
|
|
/*
|
|
* Insert a new record in the pg_group_table
|
|
*/
|
|
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, AccessExclusiveLock);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* ALTER GROUP
|
|
*/
|
|
void
|
|
AlterGroup(AlterGroupStmt *stmt, const char *tag)
|
|
{
|
|
Relation pg_group_rel;
|
|
TupleDesc pg_group_dsc;
|
|
HeapTuple group_tuple;
|
|
|
|
/*
|
|
* Make sure the user can do this.
|
|
*/
|
|
if (!superuser())
|
|
elog(ERROR, "%s: permission denied", tag);
|
|
|
|
pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
|
|
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);
|
|
|
|
/*
|
|
* 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 */
|
|
{
|
|
Datum new_record[Natts_pg_group];
|
|
char new_record_nulls[Natts_pg_group] = {' ', ' ', ' '};
|
|
ArrayType *newarray,
|
|
*oldarray;
|
|
List *newlist = NULL,
|
|
*item;
|
|
HeapTuple tuple;
|
|
bool null = false;
|
|
Datum datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
|
|
int i;
|
|
|
|
oldarray = (ArrayType *) datum;
|
|
Assert(null || ARR_NDIM(oldarray) == 1);
|
|
/* first add the old array to the hitherto empty list */
|
|
if (!null)
|
|
for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
|
|
{
|
|
int index,
|
|
arrval;
|
|
Value *v;
|
|
bool valueNull;
|
|
|
|
index = i;
|
|
arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true /* by value */ ,
|
|
sizeof(int), 0, &valueNull));
|
|
v = makeInteger(arrval);
|
|
/* filter out duplicates */
|
|
if (!member(v, newlist))
|
|
newlist = lcons(v, newlist);
|
|
}
|
|
|
|
/*
|
|
* now convert the to be added usernames to sysids and add them to
|
|
* the list
|
|
*/
|
|
foreach(item, stmt->listUsers)
|
|
{
|
|
Value *v;
|
|
|
|
if (strcmp(tag, "ALTER GROUP") == 0)
|
|
{
|
|
/* Get the uid of the proposed user to add. */
|
|
tuple = SearchSysCache(SHADOWNAME,
|
|
PointerGetDatum(strVal(lfirst(item))),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "%s: user \"%s\" does not exist",
|
|
tag, strVal(lfirst(item)));
|
|
v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
|
|
ReleaseSysCache(tuple);
|
|
}
|
|
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
|
|
*/
|
|
v = lfirst(item);
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "AlterGroup: unknown tag %s", tag);
|
|
v = NULL; /* keep compiler quiet */
|
|
}
|
|
|
|
if (!member(v, newlist))
|
|
newlist = lcons(v, newlist);
|
|
else
|
|
|
|
/*
|
|
* we silently assume here that this error will only come
|
|
* up in a ALTER GROUP statement
|
|
*/
|
|
elog(NOTICE, "%s: user \"%s\" is already in group \"%s\"", tag, strVal(lfirst(item)), stmt->name);
|
|
}
|
|
|
|
newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
|
|
newarray->size = ARR_OVERHEAD(1) + length(newlist) * 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] = length(newlist); /* axis is this long */
|
|
/* fill the array */
|
|
i = 0;
|
|
foreach(item, newlist)
|
|
((int *) ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));
|
|
|
|
/*
|
|
* Form a tuple with the new array and write it back.
|
|
*/
|
|
new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
|
|
new_record[Anum_pg_group_grosysid - 1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
|
|
new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
|
|
|
|
tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
|
|
heap_update(pg_group_rel, &group_tuple->t_self, tuple, NULL);
|
|
|
|
/* 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);
|
|
}
|
|
} /* endif alter group add user */
|
|
|
|
else if (stmt->action == -1)/* drop users from group */
|
|
{
|
|
Datum datum;
|
|
bool null;
|
|
bool is_dropuser = strcmp(tag, "DROP USER") == 0;
|
|
|
|
datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
|
|
if (null)
|
|
{
|
|
if (!is_dropuser)
|
|
elog(NOTICE, "ALTER GROUP: group \"%s\" does not have any members", stmt->name);
|
|
}
|
|
else
|
|
{
|
|
HeapTuple tuple;
|
|
Datum new_record[Natts_pg_group];
|
|
char new_record_nulls[Natts_pg_group] = {' ', ' ', ' '};
|
|
ArrayType *oldarray,
|
|
*newarray;
|
|
List *newlist = NULL,
|
|
*item;
|
|
int i;
|
|
|
|
oldarray = (ArrayType *) datum;
|
|
Assert(ARR_NDIM(oldarray) == 1);
|
|
/* first add the old array to the hitherto empty list */
|
|
for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
|
|
{
|
|
int index,
|
|
arrval;
|
|
Value *v;
|
|
bool valueNull;
|
|
|
|
index = i;
|
|
arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true /* by value */ ,
|
|
sizeof(int), 0, &valueNull));
|
|
v = makeInteger(arrval);
|
|
/* filter out duplicates */
|
|
if (!member(v, newlist))
|
|
newlist = lcons(v, newlist);
|
|
}
|
|
|
|
/*
|
|
* now convert the to be dropped usernames to sysids and
|
|
* remove them from the list
|
|
*/
|
|
foreach(item, stmt->listUsers)
|
|
{
|
|
Value *v;
|
|
|
|
if (!is_dropuser)
|
|
{
|
|
/* Get the uid of the proposed user to drop. */
|
|
tuple = SearchSysCache(SHADOWNAME,
|
|
PointerGetDatum(strVal(lfirst(item))),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "ALTER GROUP: user \"%s\" does not exist", strVal(lfirst(item)));
|
|
v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
|
|
ReleaseSysCache(tuple);
|
|
}
|
|
else
|
|
{
|
|
/* for dropuser we already know the uid */
|
|
v = lfirst(item);
|
|
}
|
|
if (member(v, newlist))
|
|
newlist = LispRemove(v, newlist);
|
|
else if (!is_dropuser)
|
|
elog(NOTICE, "ALTER GROUP: user \"%s\" is not in group \"%s\"", strVal(lfirst(item)), stmt->name);
|
|
}
|
|
|
|
newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
|
|
newarray->size = ARR_OVERHEAD(1) + length(newlist) * 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] = length(newlist); /* axis is this long */
|
|
/* fill the array */
|
|
i = 0;
|
|
foreach(item, newlist)
|
|
((int *) ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));
|
|
|
|
/*
|
|
* Insert the new tuple with the updated user list
|
|
*/
|
|
new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
|
|
new_record[Anum_pg_group_grosysid - 1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
|
|
new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
|
|
|
|
tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
|
|
heap_update(pg_group_rel, &group_tuple->t_self, tuple, NULL);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
} /* endif group not null */
|
|
} /* endif alter group drop user */
|
|
|
|
ReleaseSysCache(group_tuple);
|
|
|
|
heap_close(pg_group_rel, AccessExclusiveLock);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* DROP GROUP
|
|
*/
|
|
void
|
|
DropGroup(DropGroupStmt *stmt)
|
|
{
|
|
Relation pg_group_rel;
|
|
HeapScanDesc scan;
|
|
HeapTuple tuple;
|
|
TupleDesc pg_group_dsc;
|
|
bool gro_exists = false;
|
|
|
|
/*
|
|
* Make sure the user can do this.
|
|
*/
|
|
if (!superuser())
|
|
elog(ERROR, "DROP GROUP: permission denied");
|
|
|
|
/*
|
|
* Scan the pg_group table and delete all matching groups.
|
|
*/
|
|
pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
|
|
pg_group_dsc = RelationGetDescr(pg_group_rel);
|
|
scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
|
|
|
|
while (HeapTupleIsValid(tuple = heap_getnext(scan, false)))
|
|
{
|
|
Datum datum;
|
|
bool null;
|
|
|
|
datum = heap_getattr(tuple, Anum_pg_group_groname, pg_group_dsc, &null);
|
|
if (datum && !null && strcmp((char *) datum, stmt->name) == 0)
|
|
{
|
|
gro_exists = true;
|
|
heap_delete(pg_group_rel, &tuple->t_self, NULL);
|
|
}
|
|
}
|
|
|
|
heap_endscan(scan);
|
|
|
|
/*
|
|
* Did we find any?
|
|
*/
|
|
if (!gro_exists)
|
|
{
|
|
heap_close(pg_group_rel, AccessExclusiveLock);
|
|
elog(ERROR, "DROP GROUP: group \"%s\" does not exist", stmt->name);
|
|
}
|
|
|
|
heap_close(pg_group_rel, AccessExclusiveLock);
|
|
}
|