1 /*-------------------------------------------------------------------------
4 * Commands for manipulating users and groups.
6 * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
9 * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.151 2005/04/14 20:03:24 tgl Exp $
11 *-------------------------------------------------------------------------
15 #include "access/heapam.h"
16 #include "catalog/indexing.h"
17 #include "catalog/pg_database.h"
18 #include "catalog/pg_group.h"
19 #include "catalog/pg_shadow.h"
20 #include "catalog/pg_type.h"
21 #include "commands/user.h"
22 #include "libpq/crypt.h"
23 #include "miscadmin.h"
24 #include "utils/acl.h"
25 #include "utils/builtins.h"
26 #include "utils/flatfiles.h"
27 #include "utils/fmgroids.h"
28 #include "utils/guc.h"
29 #include "utils/lsyscache.h"
30 #include "utils/syscache.h"
33 extern bool Password_encryption;
36 static void CheckPgUserAclNotNull(void);
37 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
39 static IdList *IdListToArray(List *members);
40 static List *IdArrayToList(IdList *oldarray);
47 CreateUser(CreateUserStmt *stmt)
49 Relation pg_shadow_rel;
50 TupleDesc pg_shadow_dsc;
53 Datum new_record[Natts_pg_shadow];
54 char new_record_nulls[Natts_pg_shadow];
55 bool user_exists = false,
61 char *password = NULL; /* PostgreSQL user password */
62 bool encrypt_password = Password_encryption; /* encrypt password? */
63 char encrypted_password[MD5_PASSWD_LEN + 1];
64 int sysid = 0; /* PgSQL system id (valid if havesysid) */
65 bool createdb = false; /* Can the user create databases? */
66 bool createuser = false; /* Can this user create users? */
67 List *groupElts = NIL; /* The groups the user is a member of */
68 char *validUntil = NULL; /* The time the login is valid
70 DefElem *dpassword = NULL;
71 DefElem *dsysid = NULL;
72 DefElem *dcreatedb = NULL;
73 DefElem *dcreateuser = NULL;
74 DefElem *dgroupElts = NULL;
75 DefElem *dvalidUntil = NULL;
77 /* Extract options from the statement node tree */
78 foreach(option, stmt->options)
80 DefElem *defel = (DefElem *) lfirst(option);
82 if (strcmp(defel->defname, "password") == 0 ||
83 strcmp(defel->defname, "encryptedPassword") == 0 ||
84 strcmp(defel->defname, "unencryptedPassword") == 0)
88 (errcode(ERRCODE_SYNTAX_ERROR),
89 errmsg("conflicting or redundant options")));
91 if (strcmp(defel->defname, "encryptedPassword") == 0)
92 encrypt_password = true;
93 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
94 encrypt_password = false;
96 else if (strcmp(defel->defname, "sysid") == 0)
100 (errcode(ERRCODE_SYNTAX_ERROR),
101 errmsg("conflicting or redundant options")));
104 else if (strcmp(defel->defname, "createdb") == 0)
108 (errcode(ERRCODE_SYNTAX_ERROR),
109 errmsg("conflicting or redundant options")));
112 else if (strcmp(defel->defname, "createuser") == 0)
116 (errcode(ERRCODE_SYNTAX_ERROR),
117 errmsg("conflicting or redundant options")));
120 else if (strcmp(defel->defname, "groupElts") == 0)
124 (errcode(ERRCODE_SYNTAX_ERROR),
125 errmsg("conflicting or redundant options")));
128 else if (strcmp(defel->defname, "validUntil") == 0)
132 (errcode(ERRCODE_SYNTAX_ERROR),
133 errmsg("conflicting or redundant options")));
137 elog(ERROR, "option \"%s\" not recognized",
142 createdb = intVal(dcreatedb->arg) != 0;
144 createuser = intVal(dcreateuser->arg) != 0;
147 sysid = intVal(dsysid->arg);
150 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
151 errmsg("user ID must be positive")));
155 validUntil = strVal(dvalidUntil->arg);
157 password = strVal(dpassword->arg);
159 groupElts = (List *) dgroupElts->arg;
161 /* Check some permissions first */
163 CheckPgUserAclNotNull();
167 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
168 errmsg("must be superuser to create users")));
170 if (strcmp(stmt->user, "public") == 0)
172 (errcode(ERRCODE_RESERVED_NAME),
173 errmsg("user name \"%s\" is reserved",
177 * Scan the pg_shadow relation to be certain the user or id doesn't
178 * already exist. Note we secure exclusive lock, because we also need
179 * to be sure of what the next usesysid should be, and we need to
180 * protect our eventual update of the flat password file.
182 pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock);
183 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
185 scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL);
186 max_id = 99; /* start auto-assigned ids at 100 */
187 while (!user_exists && !sysid_exists &&
188 (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
190 Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
193 user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
195 this_sysid = shadow_form->usesysid;
196 if (havesysid) /* customized id wanted */
197 sysid_exists = (this_sysid == sysid);
201 if (this_sysid > max_id)
209 (errcode(ERRCODE_DUPLICATE_OBJECT),
210 errmsg("user \"%s\" already exists",
214 (errcode(ERRCODE_DUPLICATE_OBJECT),
215 errmsg("user ID %d is already assigned", sysid)));
217 /* If no sysid given, use max existing id + 1 */
222 * Build a tuple to insert
224 MemSet(new_record, 0, sizeof(new_record));
225 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
227 new_record[Anum_pg_shadow_usename - 1] =
228 DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
229 new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
230 AssertState(BoolIsValid(createdb));
231 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
232 AssertState(BoolIsValid(createuser));
233 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
234 /* superuser gets catupd right by default */
235 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
239 if (!encrypt_password || isMD5(password))
240 new_record[Anum_pg_shadow_passwd - 1] =
241 DirectFunctionCall1(textin, CStringGetDatum(password));
244 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
246 elog(ERROR, "password encryption failed");
247 new_record[Anum_pg_shadow_passwd - 1] =
248 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
252 new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
255 new_record[Anum_pg_shadow_valuntil - 1] =
256 DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
258 new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
260 new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
262 tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
265 * Insert new record in the pg_shadow table
267 simple_heap_insert(pg_shadow_rel, tuple);
270 CatalogUpdateIndexes(pg_shadow_rel, tuple);
273 * Add the user to the groups specified. We'll just call the below
274 * AlterGroup for this.
276 foreach(item, groupElts)
280 ags.name = strVal(lfirst(item)); /* the group name to add
283 ags.listUsers = list_make1(makeInteger(sysid));
284 AlterGroup(&ags, "CREATE USER");
288 * Now we can clean up; but keep lock until commit (to avoid possible
289 * deadlock when commit code tries to acquire lock).
291 heap_close(pg_shadow_rel, NoLock);
294 * Set flag to update flat password file at commit.
296 user_file_update_needed();
305 AlterUser(AlterUserStmt *stmt)
307 Datum new_record[Natts_pg_shadow];
308 char new_record_nulls[Natts_pg_shadow];
309 char new_record_repl[Natts_pg_shadow];
310 Relation pg_shadow_rel;
311 TupleDesc pg_shadow_dsc;
315 char *password = NULL; /* PostgreSQL user password */
316 bool encrypt_password = Password_encryption; /* encrypt password? */
317 char encrypted_password[MD5_PASSWD_LEN + 1];
318 int createdb = -1; /* Can the user create databases? */
319 int createuser = -1; /* Can this user create users? */
320 char *validUntil = NULL; /* The time the login is valid
322 DefElem *dpassword = NULL;
323 DefElem *dcreatedb = NULL;
324 DefElem *dcreateuser = NULL;
325 DefElem *dvalidUntil = NULL;
327 /* Extract options from the statement node tree */
328 foreach(option, stmt->options)
330 DefElem *defel = (DefElem *) lfirst(option);
332 if (strcmp(defel->defname, "password") == 0 ||
333 strcmp(defel->defname, "encryptedPassword") == 0 ||
334 strcmp(defel->defname, "unencryptedPassword") == 0)
338 (errcode(ERRCODE_SYNTAX_ERROR),
339 errmsg("conflicting or redundant options")));
341 if (strcmp(defel->defname, "encryptedPassword") == 0)
342 encrypt_password = true;
343 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
344 encrypt_password = false;
346 else if (strcmp(defel->defname, "createdb") == 0)
350 (errcode(ERRCODE_SYNTAX_ERROR),
351 errmsg("conflicting or redundant options")));
354 else if (strcmp(defel->defname, "createuser") == 0)
358 (errcode(ERRCODE_SYNTAX_ERROR),
359 errmsg("conflicting or redundant options")));
362 else if (strcmp(defel->defname, "validUntil") == 0)
366 (errcode(ERRCODE_SYNTAX_ERROR),
367 errmsg("conflicting or redundant options")));
371 elog(ERROR, "option \"%s\" not recognized",
376 createdb = intVal(dcreatedb->arg);
378 createuser = intVal(dcreateuser->arg);
380 validUntil = strVal(dvalidUntil->arg);
382 password = strVal(dpassword->arg);
385 CheckPgUserAclNotNull();
387 /* must be superuser or just want to change your own password */
393 strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0))
395 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
396 errmsg("permission denied")));
399 * Scan the pg_shadow relation to be certain the user exists. Note we
400 * secure exclusive lock to protect our update of the flat password
403 pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock);
404 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
406 tuple = SearchSysCache(SHADOWNAME,
407 PointerGetDatum(stmt->user),
409 if (!HeapTupleIsValid(tuple))
411 (errcode(ERRCODE_UNDEFINED_OBJECT),
412 errmsg("user \"%s\" does not exist", stmt->user)));
415 * Build an updated tuple, perusing the information just obtained
417 MemSet(new_record, 0, sizeof(new_record));
418 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
419 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
421 new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
422 CStringGetDatum(stmt->user));
423 new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
428 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
429 new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
433 * createuser (superuser) and catupd
435 * XXX It's rather unclear how to handle catupd. It's probably best to
436 * keep it equal to the superuser status, otherwise you could end up
437 * with a situation where no existing superuser can alter the
438 * catalogs, including pg_shadow!
442 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
443 new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
445 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
446 new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
452 if (!encrypt_password || isMD5(password))
453 new_record[Anum_pg_shadow_passwd - 1] =
454 DirectFunctionCall1(textin, CStringGetDatum(password));
457 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
459 elog(ERROR, "password encryption failed");
460 new_record[Anum_pg_shadow_passwd - 1] =
461 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
463 new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
469 new_record[Anum_pg_shadow_valuntil - 1] =
470 DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
471 new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
474 new_tuple = heap_modifytuple(tuple, pg_shadow_dsc, new_record,
475 new_record_nulls, new_record_repl);
476 simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
479 CatalogUpdateIndexes(pg_shadow_rel, new_tuple);
481 ReleaseSysCache(tuple);
482 heap_freetuple(new_tuple);
485 * Now we can clean up; but keep lock until commit (to avoid possible
486 * deadlock when commit code tries to acquire lock).
488 heap_close(pg_shadow_rel, NoLock);
491 * Set flag to update flat password file at commit.
493 user_file_update_needed();
501 AlterUserSet(AlterUserSetStmt *stmt)
507 Datum repl_val[Natts_pg_shadow];
508 char repl_null[Natts_pg_shadow];
509 char repl_repl[Natts_pg_shadow];
512 valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
515 * RowExclusiveLock is sufficient, because we don't need to update the
516 * flat password file.
518 rel = heap_open(ShadowRelationId, RowExclusiveLock);
519 oldtuple = SearchSysCache(SHADOWNAME,
520 PointerGetDatum(stmt->user),
522 if (!HeapTupleIsValid(oldtuple))
524 (errcode(ERRCODE_UNDEFINED_OBJECT),
525 errmsg("user \"%s\" does not exist", stmt->user)));
528 ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
530 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
531 errmsg("permission denied")));
533 for (i = 0; i < Natts_pg_shadow; i++)
536 repl_repl[Anum_pg_shadow_useconfig - 1] = 'r';
537 if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
540 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
548 repl_null[Anum_pg_shadow_useconfig - 1] = ' ';
550 datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
551 Anum_pg_shadow_useconfig, &isnull);
553 array = isnull ? NULL : DatumGetArrayTypeP(datum);
556 array = GUCArrayAdd(array, stmt->variable, valuestr);
558 array = GUCArrayDelete(array, stmt->variable);
561 repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array);
563 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
566 newtuple = heap_modifytuple(oldtuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
567 simple_heap_update(rel, &oldtuple->t_self, newtuple);
569 CatalogUpdateIndexes(rel, newtuple);
571 ReleaseSysCache(oldtuple);
572 heap_close(rel, RowExclusiveLock);
580 DropUser(DropUserStmt *stmt)
582 Relation pg_shadow_rel;
583 TupleDesc pg_shadow_dsc;
588 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
589 errmsg("must be superuser to drop users")));
592 * Scan the pg_shadow relation to find the usesysid of the user to be
593 * deleted. Note we secure exclusive lock, because we need to protect
594 * our update of the flat password file.
596 pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock);
597 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
599 foreach(item, stmt->users)
601 const char *user = strVal(lfirst(item));
610 tuple = SearchSysCache(SHADOWNAME,
611 PointerGetDatum(user),
613 if (!HeapTupleIsValid(tuple))
615 (errcode(ERRCODE_UNDEFINED_OBJECT),
616 errmsg("user \"%s\" does not exist", user)));
618 usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
620 if (usesysid == GetUserId())
622 (errcode(ERRCODE_OBJECT_IN_USE),
623 errmsg("current user cannot be dropped")));
624 if (usesysid == GetSessionUserId())
626 (errcode(ERRCODE_OBJECT_IN_USE),
627 errmsg("session user cannot be dropped")));
630 * Check if user still owns a database. If so, error out.
632 * (It used to be that this function would drop the database
633 * automatically. This is not only very dangerous for people that
634 * don't read the manual, it doesn't seem to be the behaviour one
635 * would expect either.) -- petere 2000/01/14)
637 pg_rel = heap_open(DatabaseRelationId, AccessShareLock);
638 pg_dsc = RelationGetDescr(pg_rel);
640 ScanKeyInit(&scankey,
641 Anum_pg_database_datdba,
642 BTEqualStrategyNumber, F_INT4EQ,
643 Int32GetDatum(usesysid));
645 scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
647 if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
651 dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
653 (errcode(ERRCODE_OBJECT_IN_USE),
654 errmsg("user \"%s\" cannot be dropped", user),
655 errdetail("The user owns database \"%s\".", dbname)));
659 heap_close(pg_rel, AccessShareLock);
662 * Somehow we'd have to check for tables, views, etc. owned by the
663 * user as well, but those could be spread out over all sorts of
664 * databases which we don't have access to (easily).
668 * Remove the user from the pg_shadow table
670 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
672 ReleaseSysCache(tuple);
675 * Remove user from groups
677 * try calling alter group drop user for every group
679 pg_rel = heap_open(GroupRelationId, ExclusiveLock);
680 pg_dsc = RelationGetDescr(pg_rel);
681 scan = heap_beginscan(pg_rel, SnapshotNow, 0, NULL);
682 while ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
686 /* the group name from which to try to drop the user: */
687 ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
689 ags.listUsers = list_make1(makeInteger(usesysid));
690 AlterGroup(&ags, "DROP USER");
693 heap_close(pg_rel, ExclusiveLock);
696 * Advance command counter so that later iterations of this loop
697 * will see the changes already made. This is essential if, for
698 * example, we are trying to drop two users who are members of the
699 * same group --- the AlterGroup for the second user had better
700 * see the tuple updated from the first one.
702 CommandCounterIncrement();
706 * Now we can clean up; but keep lock until commit (to avoid possible
707 * deadlock when commit code tries to acquire lock).
709 heap_close(pg_shadow_rel, NoLock);
712 * Set flag to update flat password file at commit.
714 user_file_update_needed();
722 RenameUser(const char *oldname, const char *newname)
730 Datum repl_val[Natts_pg_shadow];
731 char repl_null[Natts_pg_shadow];
732 char repl_repl[Natts_pg_shadow];
735 /* ExclusiveLock because we need to update the password file */
736 rel = heap_open(ShadowRelationId, ExclusiveLock);
737 dsc = RelationGetDescr(rel);
739 oldtuple = SearchSysCache(SHADOWNAME,
740 CStringGetDatum(oldname),
742 if (!HeapTupleIsValid(oldtuple))
744 (errcode(ERRCODE_UNDEFINED_OBJECT),
745 errmsg("user \"%s\" does not exist", oldname)));
748 * XXX Client applications probably store the session user somewhere,
749 * so renaming it could cause confusion. On the other hand, there may
750 * not be an actual problem besides a little confusion, so think about
753 if (((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetSessionUserId())
755 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
756 errmsg("session user may not be renamed")));
758 /* make sure the new name doesn't exist */
759 if (SearchSysCacheExists(SHADOWNAME,
760 CStringGetDatum(newname),
763 (errcode(ERRCODE_DUPLICATE_OBJECT),
764 errmsg("user \"%s\" already exists", newname)));
766 /* must be superuser */
769 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
770 errmsg("must be superuser to rename users")));
772 for (i = 0; i < Natts_pg_shadow; i++)
775 repl_repl[Anum_pg_shadow_usename - 1] = 'r';
776 repl_val[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
777 CStringGetDatum(newname));
778 repl_null[Anum_pg_shadow_usename - 1] = ' ';
780 datum = heap_getattr(oldtuple, Anum_pg_shadow_passwd, dsc, &isnull);
782 if (!isnull && isMD5(DatumGetCString(DirectFunctionCall1(textout, datum))))
784 /* MD5 uses the username as salt, so just clear it on a rename */
785 repl_repl[Anum_pg_shadow_passwd - 1] = 'r';
786 repl_null[Anum_pg_shadow_passwd - 1] = 'n';
789 (errmsg("MD5 password cleared because of user rename")));
792 newtuple = heap_modifytuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
793 simple_heap_update(rel, &oldtuple->t_self, newtuple);
795 CatalogUpdateIndexes(rel, newtuple);
797 ReleaseSysCache(oldtuple);
798 heap_close(rel, NoLock);
800 user_file_update_needed();
805 * CheckPgUserAclNotNull
807 * check to see if there is an ACL on pg_shadow
810 CheckPgUserAclNotNull(void)
814 htup = SearchSysCache(RELOID,
815 ObjectIdGetDatum(ShadowRelationId),
817 if (!HeapTupleIsValid(htup)) /* should not happen, we hope */
818 elog(ERROR, "cache lookup failed for relation %u", ShadowRelationId);
820 if (heap_attisnull(htup, Anum_pg_class_relacl))
822 Form_pg_class classForm = (Form_pg_class) GETSTRUCT(htup);
825 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
826 errmsg("before using passwords you must revoke privileges on %s",
827 NameStr(classForm->relname)),
828 errdetail("This restriction is to prevent unprivileged users from reading the passwords."),
829 errhint("Try REVOKE ALL ON \"%s\" FROM PUBLIC.",
830 NameStr(classForm->relname))));
833 ReleaseSysCache(htup);
841 CreateGroup(CreateGroupStmt *stmt)
843 Relation pg_group_rel;
846 TupleDesc pg_group_dsc;
847 bool group_exists = false,
848 sysid_exists = false,
851 Datum new_record[Natts_pg_group];
852 char new_record_nulls[Natts_pg_group];
858 List *userElts = NIL;
859 DefElem *dsysid = NULL;
860 DefElem *duserElts = NULL;
862 foreach(option, stmt->options)
864 DefElem *defel = (DefElem *) lfirst(option);
866 if (strcmp(defel->defname, "sysid") == 0)
870 (errcode(ERRCODE_SYNTAX_ERROR),
871 errmsg("conflicting or redundant options")));
874 else if (strcmp(defel->defname, "userElts") == 0)
878 (errcode(ERRCODE_SYNTAX_ERROR),
879 errmsg("conflicting or redundant options")));
883 elog(ERROR, "option \"%s\" not recognized",
889 sysid = intVal(dsysid->arg);
892 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
893 errmsg("group ID must be positive")));
898 userElts = (List *) duserElts->arg;
901 * Make sure the user can do this.
905 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
906 errmsg("must be superuser to create groups")));
908 if (strcmp(stmt->name, "public") == 0)
910 (errcode(ERRCODE_RESERVED_NAME),
911 errmsg("group name \"%s\" is reserved",
915 * Scan the pg_group relation to be certain the group or id doesn't
916 * already exist. Note we secure exclusive lock, because we also need
917 * to be sure of what the next grosysid should be, and we need to
918 * protect our eventual update of the flat group file.
920 pg_group_rel = heap_open(GroupRelationId, ExclusiveLock);
921 pg_group_dsc = RelationGetDescr(pg_group_rel);
923 scan = heap_beginscan(pg_group_rel, SnapshotNow, 0, NULL);
924 max_id = 99; /* start auto-assigned ids at 100 */
925 while (!group_exists && !sysid_exists &&
926 (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
928 Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
931 group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
933 this_sysid = group_form->grosysid;
934 if (havesysid) /* customized id wanted */
935 sysid_exists = (this_sysid == sysid);
939 if (this_sysid > max_id)
947 (errcode(ERRCODE_DUPLICATE_OBJECT),
948 errmsg("group \"%s\" already exists",
952 (errcode(ERRCODE_DUPLICATE_OBJECT),
953 errmsg("group ID %d is already assigned", sysid)));
955 /* If no sysid given, use max existing id + 1 */
960 * Translate the given user names to ids
962 foreach(item, userElts)
964 const char *groupuser = strVal(lfirst(item));
965 int32 userid = get_usesysid(groupuser);
967 if (!list_member_int(newlist, userid))
968 newlist = lappend_int(newlist, userid);
971 /* build an array to insert */
973 grolist = IdListToArray(newlist);
978 * Form a tuple to insert
980 new_record[Anum_pg_group_groname - 1] =
981 DirectFunctionCall1(namein, CStringGetDatum(stmt->name));
982 new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid);
983 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist);
985 new_record_nulls[Anum_pg_group_groname - 1] = ' ';
986 new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
987 new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
989 tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
992 * Insert a new record in the pg_group table
994 simple_heap_insert(pg_group_rel, tuple);
997 CatalogUpdateIndexes(pg_group_rel, tuple);
1000 * Now we can clean up; but keep lock until commit (to avoid possible
1001 * deadlock when commit code tries to acquire lock).
1003 heap_close(pg_group_rel, NoLock);
1006 * Set flag to update flat group file at commit.
1008 group_file_update_needed();
1016 AlterGroup(AlterGroupStmt *stmt, const char *tag)
1018 Relation pg_group_rel;
1019 TupleDesc pg_group_dsc;
1020 HeapTuple group_tuple;
1028 * Make sure the user can do this.
1032 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1033 errmsg("must be superuser to alter groups")));
1036 * Secure exclusive lock to protect our update of the flat group file.
1038 pg_group_rel = heap_open(GroupRelationId, ExclusiveLock);
1039 pg_group_dsc = RelationGetDescr(pg_group_rel);
1042 * Fetch existing tuple for group.
1044 group_tuple = SearchSysCache(GRONAME,
1045 PointerGetDatum(stmt->name),
1047 if (!HeapTupleIsValid(group_tuple))
1049 (errcode(ERRCODE_UNDEFINED_OBJECT),
1050 errmsg("group \"%s\" does not exist", stmt->name)));
1052 /* Fetch old group membership. */
1053 datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
1054 pg_group_dsc, &null);
1055 oldarray = null ? NULL : DatumGetIdListP(datum);
1057 /* initialize list with old array contents */
1058 newlist = IdArrayToList(oldarray);
1061 * Now decide what to do.
1063 AssertState(stmt->action == +1 || stmt->action == -1);
1065 if (stmt->action == +1) /* add users, might also be invoked by
1069 * convert the to be added usernames to sysids and add them to the
1072 foreach(item, stmt->listUsers)
1076 if (strcmp(tag, "ALTER GROUP") == 0)
1078 /* Get the uid of the proposed user to add. */
1079 sysid = get_usesysid(strVal(lfirst(item)));
1081 else if (strcmp(tag, "CREATE USER") == 0)
1084 * in this case we already know the uid and it wouldn't be
1085 * in the cache anyway yet
1087 sysid = intVal(lfirst(item));
1091 elog(ERROR, "unexpected tag: \"%s\"", tag);
1092 sysid = 0; /* keep compiler quiet */
1095 if (!list_member_int(newlist, sysid))
1096 newlist = lappend_int(newlist, sysid);
1100 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1101 } /* endif alter group add user */
1103 else if (stmt->action == -1) /* drop users from group */
1105 bool is_dropuser = strcmp(tag, "DROP USER") == 0;
1111 (errcode(ERRCODE_WARNING),
1112 errmsg("group \"%s\" does not have any members",
1118 * convert the to be dropped usernames to sysids and remove
1119 * them from the list
1121 foreach(item, stmt->listUsers)
1127 /* Get the uid of the proposed user to drop. */
1128 sysid = get_usesysid(strVal(lfirst(item)));
1132 /* for dropuser we already know the uid */
1133 sysid = intVal(lfirst(item));
1135 if (list_member_int(newlist, sysid))
1136 newlist = list_delete_int(newlist, sysid);
1137 else if (!is_dropuser)
1139 (errcode(ERRCODE_WARNING),
1140 errmsg("user \"%s\" is not in group \"%s\"",
1141 strVal(lfirst(item)), stmt->name)));
1145 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1146 } /* endif group not null */
1147 } /* endif alter group drop user */
1149 ReleaseSysCache(group_tuple);
1152 * Now we can clean up; but keep lock until commit (to avoid possible
1153 * deadlock when commit code tries to acquire lock).
1155 heap_close(pg_group_rel, NoLock);
1158 * Set flag to update flat group file at commit.
1160 group_file_update_needed();
1164 * Subroutine for AlterGroup: given a pg_group tuple and a desired new
1165 * membership (expressed as an integer list), form and write an updated tuple.
1166 * The pg_group relation must be open and locked already.
1169 UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
1173 Datum new_record[Natts_pg_group];
1174 char new_record_nulls[Natts_pg_group];
1175 char new_record_repl[Natts_pg_group];
1178 newarray = IdListToArray(members);
1181 * Form an updated tuple with the new array and write it back.
1183 MemSet(new_record, 0, sizeof(new_record));
1184 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1185 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1187 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1188 new_record_repl[Anum_pg_group_grolist - 1] = 'r';
1190 tuple = heap_modifytuple(group_tuple, RelationGetDescr(group_rel),
1191 new_record, new_record_nulls, new_record_repl);
1193 simple_heap_update(group_rel, &group_tuple->t_self, tuple);
1195 /* Update indexes */
1196 CatalogUpdateIndexes(group_rel, tuple);
1201 * Convert an integer list of sysids to an array.
1204 IdListToArray(List *members)
1206 int nmembers = list_length(members);
1211 newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
1212 newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
1213 newarray->flags = 0;
1214 newarray->elemtype = INT4OID;
1215 ARR_NDIM(newarray) = 1; /* one dimensional array */
1216 ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */
1217 ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */
1219 foreach(item, members)
1220 ((int *) ARR_DATA_PTR(newarray))[i++] = lfirst_int(item);
1226 * Convert an array of sysids to an integer list.
1229 IdArrayToList(IdList *oldarray)
1231 List *newlist = NIL;
1235 if (oldarray == NULL)
1238 Assert(ARR_NDIM(oldarray) == 1);
1239 Assert(ARR_ELEMTYPE(oldarray) == INT4OID);
1241 hibound = ARR_DIMS(oldarray)[0];
1243 for (i = 0; i < hibound; i++)
1247 sysid = ((int32 *) ARR_DATA_PTR(oldarray))[i];
1248 /* filter out any duplicates --- probably a waste of time */
1249 if (!list_member_int(newlist, sysid))
1250 newlist = lappend_int(newlist, sysid);
1261 DropGroup(DropGroupStmt *stmt)
1263 Relation pg_group_rel;
1267 * Make sure the user can do this.
1271 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1272 errmsg("must be superuser to drop groups")));
1275 * Secure exclusive lock to protect our update of the flat group file.
1277 pg_group_rel = heap_open(GroupRelationId, ExclusiveLock);
1279 /* Find and delete the group. */
1281 tuple = SearchSysCacheCopy(GRONAME,
1282 PointerGetDatum(stmt->name),
1284 if (!HeapTupleIsValid(tuple))
1286 (errcode(ERRCODE_UNDEFINED_OBJECT),
1287 errmsg("group \"%s\" does not exist", stmt->name)));
1289 simple_heap_delete(pg_group_rel, &tuple->t_self);
1292 * Now we can clean up; but keep lock until commit (to avoid possible
1293 * deadlock when commit code tries to acquire lock).
1295 heap_close(pg_group_rel, NoLock);
1298 * Set flag to update flat group file at commit.
1300 group_file_update_needed();
1308 RenameGroup(const char *oldname, const char *newname)
1313 /* ExclusiveLock because we need to update the flat group file */
1314 rel = heap_open(GroupRelationId, ExclusiveLock);
1316 tup = SearchSysCacheCopy(GRONAME,
1317 CStringGetDatum(oldname),
1319 if (!HeapTupleIsValid(tup))
1321 (errcode(ERRCODE_UNDEFINED_OBJECT),
1322 errmsg("group \"%s\" does not exist", oldname)));
1324 /* make sure the new name doesn't exist */
1325 if (SearchSysCacheExists(GRONAME,
1326 CStringGetDatum(newname),
1329 (errcode(ERRCODE_DUPLICATE_OBJECT),
1330 errmsg("group \"%s\" already exists", newname)));
1332 /* must be superuser */
1335 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1336 errmsg("must be superuser to rename groups")));
1339 namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
1340 simple_heap_update(rel, &tup->t_self, tup);
1341 CatalogUpdateIndexes(rel, tup);
1343 heap_close(rel, NoLock);
1344 heap_freetuple(tup);
1346 group_file_update_needed();