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.149 2005/02/20 02:21:34 tgl Exp $
11 *-------------------------------------------------------------------------
15 #include "access/heapam.h"
16 #include "catalog/catname.h"
17 #include "catalog/indexing.h"
18 #include "catalog/pg_database.h"
19 #include "catalog/pg_group.h"
20 #include "catalog/pg_shadow.h"
21 #include "catalog/pg_type.h"
22 #include "commands/user.h"
23 #include "libpq/crypt.h"
24 #include "miscadmin.h"
25 #include "utils/acl.h"
26 #include "utils/builtins.h"
27 #include "utils/flatfiles.h"
28 #include "utils/fmgroids.h"
29 #include "utils/guc.h"
30 #include "utils/lsyscache.h"
31 #include "utils/syscache.h"
34 extern bool Password_encryption;
37 static void CheckPgUserAclNotNull(void);
38 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
40 static IdList *IdListToArray(List *members);
41 static List *IdArrayToList(IdList *oldarray);
48 CreateUser(CreateUserStmt *stmt)
50 Relation pg_shadow_rel;
51 TupleDesc pg_shadow_dsc;
54 Datum new_record[Natts_pg_shadow];
55 char new_record_nulls[Natts_pg_shadow];
56 bool user_exists = false,
62 char *password = NULL; /* PostgreSQL user password */
63 bool encrypt_password = Password_encryption; /* encrypt password? */
64 char encrypted_password[MD5_PASSWD_LEN + 1];
65 int sysid = 0; /* PgSQL system id (valid if havesysid) */
66 bool createdb = false; /* Can the user create databases? */
67 bool createuser = false; /* Can this user create users? */
68 List *groupElts = NIL; /* The groups the user is a member of */
69 char *validUntil = NULL; /* The time the login is valid
71 DefElem *dpassword = NULL;
72 DefElem *dsysid = NULL;
73 DefElem *dcreatedb = NULL;
74 DefElem *dcreateuser = NULL;
75 DefElem *dgroupElts = NULL;
76 DefElem *dvalidUntil = NULL;
78 /* Extract options from the statement node tree */
79 foreach(option, stmt->options)
81 DefElem *defel = (DefElem *) lfirst(option);
83 if (strcmp(defel->defname, "password") == 0 ||
84 strcmp(defel->defname, "encryptedPassword") == 0 ||
85 strcmp(defel->defname, "unencryptedPassword") == 0)
89 (errcode(ERRCODE_SYNTAX_ERROR),
90 errmsg("conflicting or redundant options")));
92 if (strcmp(defel->defname, "encryptedPassword") == 0)
93 encrypt_password = true;
94 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
95 encrypt_password = false;
97 else if (strcmp(defel->defname, "sysid") == 0)
101 (errcode(ERRCODE_SYNTAX_ERROR),
102 errmsg("conflicting or redundant options")));
105 else if (strcmp(defel->defname, "createdb") == 0)
109 (errcode(ERRCODE_SYNTAX_ERROR),
110 errmsg("conflicting or redundant options")));
113 else if (strcmp(defel->defname, "createuser") == 0)
117 (errcode(ERRCODE_SYNTAX_ERROR),
118 errmsg("conflicting or redundant options")));
121 else if (strcmp(defel->defname, "groupElts") == 0)
125 (errcode(ERRCODE_SYNTAX_ERROR),
126 errmsg("conflicting or redundant options")));
129 else if (strcmp(defel->defname, "validUntil") == 0)
133 (errcode(ERRCODE_SYNTAX_ERROR),
134 errmsg("conflicting or redundant options")));
138 elog(ERROR, "option \"%s\" not recognized",
143 createdb = intVal(dcreatedb->arg) != 0;
145 createuser = intVal(dcreateuser->arg) != 0;
148 sysid = intVal(dsysid->arg);
151 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
152 errmsg("user ID must be positive")));
156 validUntil = strVal(dvalidUntil->arg);
158 password = strVal(dpassword->arg);
160 groupElts = (List *) dgroupElts->arg;
162 /* Check some permissions first */
164 CheckPgUserAclNotNull();
168 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
169 errmsg("must be superuser to create users")));
171 if (strcmp(stmt->user, "public") == 0)
173 (errcode(ERRCODE_RESERVED_NAME),
174 errmsg("user name \"%s\" is reserved",
178 * Scan the pg_shadow relation to be certain the user or id doesn't
179 * already exist. Note we secure exclusive lock, because we also need
180 * to be sure of what the next usesysid should be, and we need to
181 * protect our eventual update of the flat password file.
183 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
184 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
186 scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL);
187 max_id = 99; /* start auto-assigned ids at 100 */
188 while (!user_exists && !sysid_exists &&
189 (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
191 Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
194 user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
196 this_sysid = shadow_form->usesysid;
197 if (havesysid) /* customized id wanted */
198 sysid_exists = (this_sysid == sysid);
202 if (this_sysid > max_id)
210 (errcode(ERRCODE_DUPLICATE_OBJECT),
211 errmsg("user \"%s\" already exists",
215 (errcode(ERRCODE_DUPLICATE_OBJECT),
216 errmsg("user ID %d is already assigned", sysid)));
218 /* If no sysid given, use max existing id + 1 */
223 * Build a tuple to insert
225 MemSet(new_record, 0, sizeof(new_record));
226 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
228 new_record[Anum_pg_shadow_usename - 1] =
229 DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
230 new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
231 AssertState(BoolIsValid(createdb));
232 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
233 AssertState(BoolIsValid(createuser));
234 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
235 /* superuser gets catupd right by default */
236 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
240 if (!encrypt_password || isMD5(password))
241 new_record[Anum_pg_shadow_passwd - 1] =
242 DirectFunctionCall1(textin, CStringGetDatum(password));
245 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
247 elog(ERROR, "password encryption failed");
248 new_record[Anum_pg_shadow_passwd - 1] =
249 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
253 new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
256 new_record[Anum_pg_shadow_valuntil - 1] =
257 DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
259 new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
261 new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
263 tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
266 * Insert new record in the pg_shadow table
268 simple_heap_insert(pg_shadow_rel, tuple);
271 CatalogUpdateIndexes(pg_shadow_rel, tuple);
274 * Add the user to the groups specified. We'll just call the below
275 * AlterGroup for this.
277 foreach(item, groupElts)
281 ags.name = strVal(lfirst(item)); /* the group name to add
284 ags.listUsers = list_make1(makeInteger(sysid));
285 AlterGroup(&ags, "CREATE USER");
289 * Now we can clean up; but keep lock until commit (to avoid possible
290 * deadlock when commit code tries to acquire lock).
292 heap_close(pg_shadow_rel, NoLock);
295 * Set flag to update flat password file at commit.
297 user_file_update_needed();
306 AlterUser(AlterUserStmt *stmt)
308 Datum new_record[Natts_pg_shadow];
309 char new_record_nulls[Natts_pg_shadow];
310 char new_record_repl[Natts_pg_shadow];
311 Relation pg_shadow_rel;
312 TupleDesc pg_shadow_dsc;
316 char *password = NULL; /* PostgreSQL user password */
317 bool encrypt_password = Password_encryption; /* encrypt password? */
318 char encrypted_password[MD5_PASSWD_LEN + 1];
319 int createdb = -1; /* Can the user create databases? */
320 int createuser = -1; /* Can this user create users? */
321 char *validUntil = NULL; /* The time the login is valid
323 DefElem *dpassword = NULL;
324 DefElem *dcreatedb = NULL;
325 DefElem *dcreateuser = NULL;
326 DefElem *dvalidUntil = NULL;
328 /* Extract options from the statement node tree */
329 foreach(option, stmt->options)
331 DefElem *defel = (DefElem *) lfirst(option);
333 if (strcmp(defel->defname, "password") == 0 ||
334 strcmp(defel->defname, "encryptedPassword") == 0 ||
335 strcmp(defel->defname, "unencryptedPassword") == 0)
339 (errcode(ERRCODE_SYNTAX_ERROR),
340 errmsg("conflicting or redundant options")));
342 if (strcmp(defel->defname, "encryptedPassword") == 0)
343 encrypt_password = true;
344 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
345 encrypt_password = false;
347 else if (strcmp(defel->defname, "createdb") == 0)
351 (errcode(ERRCODE_SYNTAX_ERROR),
352 errmsg("conflicting or redundant options")));
355 else if (strcmp(defel->defname, "createuser") == 0)
359 (errcode(ERRCODE_SYNTAX_ERROR),
360 errmsg("conflicting or redundant options")));
363 else if (strcmp(defel->defname, "validUntil") == 0)
367 (errcode(ERRCODE_SYNTAX_ERROR),
368 errmsg("conflicting or redundant options")));
372 elog(ERROR, "option \"%s\" not recognized",
377 createdb = intVal(dcreatedb->arg);
379 createuser = intVal(dcreateuser->arg);
381 validUntil = strVal(dvalidUntil->arg);
383 password = strVal(dpassword->arg);
386 CheckPgUserAclNotNull();
388 /* must be superuser or just want to change your own password */
394 strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0))
396 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
397 errmsg("permission denied")));
400 * Scan the pg_shadow relation to be certain the user exists. Note we
401 * secure exclusive lock to protect our update of the flat password
404 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
405 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
407 tuple = SearchSysCache(SHADOWNAME,
408 PointerGetDatum(stmt->user),
410 if (!HeapTupleIsValid(tuple))
412 (errcode(ERRCODE_UNDEFINED_OBJECT),
413 errmsg("user \"%s\" does not exist", stmt->user)));
416 * Build an updated tuple, perusing the information just obtained
418 MemSet(new_record, 0, sizeof(new_record));
419 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
420 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
422 new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
423 CStringGetDatum(stmt->user));
424 new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
429 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
430 new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
434 * createuser (superuser) and catupd
436 * XXX It's rather unclear how to handle catupd. It's probably best to
437 * keep it equal to the superuser status, otherwise you could end up
438 * with a situation where no existing superuser can alter the
439 * catalogs, including pg_shadow!
443 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
444 new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
446 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
447 new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
453 if (!encrypt_password || isMD5(password))
454 new_record[Anum_pg_shadow_passwd - 1] =
455 DirectFunctionCall1(textin, CStringGetDatum(password));
458 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
460 elog(ERROR, "password encryption failed");
461 new_record[Anum_pg_shadow_passwd - 1] =
462 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
464 new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
470 new_record[Anum_pg_shadow_valuntil - 1] =
471 DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
472 new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
475 new_tuple = heap_modifytuple(tuple, pg_shadow_dsc, new_record,
476 new_record_nulls, new_record_repl);
477 simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
480 CatalogUpdateIndexes(pg_shadow_rel, new_tuple);
482 ReleaseSysCache(tuple);
483 heap_freetuple(new_tuple);
486 * Now we can clean up; but keep lock until commit (to avoid possible
487 * deadlock when commit code tries to acquire lock).
489 heap_close(pg_shadow_rel, NoLock);
492 * Set flag to update flat password file at commit.
494 user_file_update_needed();
502 AlterUserSet(AlterUserSetStmt *stmt)
508 Datum repl_val[Natts_pg_shadow];
509 char repl_null[Natts_pg_shadow];
510 char repl_repl[Natts_pg_shadow];
513 valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
516 * RowExclusiveLock is sufficient, because we don't need to update the
517 * flat password file.
519 rel = heap_openr(ShadowRelationName, RowExclusiveLock);
520 oldtuple = SearchSysCache(SHADOWNAME,
521 PointerGetDatum(stmt->user),
523 if (!HeapTupleIsValid(oldtuple))
525 (errcode(ERRCODE_UNDEFINED_OBJECT),
526 errmsg("user \"%s\" does not exist", stmt->user)));
529 ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
531 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
532 errmsg("permission denied")));
534 for (i = 0; i < Natts_pg_shadow; i++)
537 repl_repl[Anum_pg_shadow_useconfig - 1] = 'r';
538 if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
541 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
549 repl_null[Anum_pg_shadow_useconfig - 1] = ' ';
551 datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
552 Anum_pg_shadow_useconfig, &isnull);
554 array = isnull ? NULL : DatumGetArrayTypeP(datum);
557 array = GUCArrayAdd(array, stmt->variable, valuestr);
559 array = GUCArrayDelete(array, stmt->variable);
562 repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array);
564 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
567 newtuple = heap_modifytuple(oldtuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
568 simple_heap_update(rel, &oldtuple->t_self, newtuple);
570 CatalogUpdateIndexes(rel, newtuple);
572 ReleaseSysCache(oldtuple);
573 heap_close(rel, RowExclusiveLock);
581 DropUser(DropUserStmt *stmt)
583 Relation pg_shadow_rel;
584 TupleDesc pg_shadow_dsc;
589 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
590 errmsg("must be superuser to drop users")));
593 * Scan the pg_shadow relation to find the usesysid of the user to be
594 * deleted. Note we secure exclusive lock, because we need to protect
595 * our update of the flat password file.
597 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
598 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
600 foreach(item, stmt->users)
602 const char *user = strVal(lfirst(item));
611 tuple = SearchSysCache(SHADOWNAME,
612 PointerGetDatum(user),
614 if (!HeapTupleIsValid(tuple))
616 (errcode(ERRCODE_UNDEFINED_OBJECT),
617 errmsg("user \"%s\" does not exist", user)));
619 usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
621 if (usesysid == GetUserId())
623 (errcode(ERRCODE_OBJECT_IN_USE),
624 errmsg("current user cannot be dropped")));
625 if (usesysid == GetSessionUserId())
627 (errcode(ERRCODE_OBJECT_IN_USE),
628 errmsg("session user cannot be dropped")));
631 * Check if user still owns a database. If so, error out.
633 * (It used to be that this function would drop the database
634 * automatically. This is not only very dangerous for people that
635 * don't read the manual, it doesn't seem to be the behaviour one
636 * would expect either.) -- petere 2000/01/14)
638 pg_rel = heap_openr(DatabaseRelationName, AccessShareLock);
639 pg_dsc = RelationGetDescr(pg_rel);
641 ScanKeyInit(&scankey,
642 Anum_pg_database_datdba,
643 BTEqualStrategyNumber, F_INT4EQ,
644 Int32GetDatum(usesysid));
646 scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
648 if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
652 dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
654 (errcode(ERRCODE_OBJECT_IN_USE),
655 errmsg("user \"%s\" cannot be dropped", user),
656 errdetail("The user owns database \"%s\".", dbname)));
660 heap_close(pg_rel, AccessShareLock);
663 * Somehow we'd have to check for tables, views, etc. owned by the
664 * user as well, but those could be spread out over all sorts of
665 * databases which we don't have access to (easily).
669 * Remove the user from the pg_shadow table
671 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
673 ReleaseSysCache(tuple);
676 * Remove user from groups
678 * try calling alter group drop user for every group
680 pg_rel = heap_openr(GroupRelationName, ExclusiveLock);
681 pg_dsc = RelationGetDescr(pg_rel);
682 scan = heap_beginscan(pg_rel, SnapshotNow, 0, NULL);
683 while ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
687 /* the group name from which to try to drop the user: */
688 ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
690 ags.listUsers = list_make1(makeInteger(usesysid));
691 AlterGroup(&ags, "DROP USER");
694 heap_close(pg_rel, ExclusiveLock);
697 * Advance command counter so that later iterations of this loop
698 * will see the changes already made. This is essential if, for
699 * example, we are trying to drop two users who are members of the
700 * same group --- the AlterGroup for the second user had better
701 * see the tuple updated from the first one.
703 CommandCounterIncrement();
707 * Now we can clean up; but keep lock until commit (to avoid possible
708 * deadlock when commit code tries to acquire lock).
710 heap_close(pg_shadow_rel, NoLock);
713 * Set flag to update flat password file at commit.
715 user_file_update_needed();
723 RenameUser(const char *oldname, const char *newname)
731 Datum repl_val[Natts_pg_shadow];
732 char repl_null[Natts_pg_shadow];
733 char repl_repl[Natts_pg_shadow];
736 /* ExclusiveLock because we need to update the password file */
737 rel = heap_openr(ShadowRelationName, ExclusiveLock);
738 dsc = RelationGetDescr(rel);
740 oldtuple = SearchSysCache(SHADOWNAME,
741 CStringGetDatum(oldname),
743 if (!HeapTupleIsValid(oldtuple))
745 (errcode(ERRCODE_UNDEFINED_OBJECT),
746 errmsg("user \"%s\" does not exist", oldname)));
749 * XXX Client applications probably store the session user somewhere,
750 * so renaming it could cause confusion. On the other hand, there may
751 * not be an actual problem besides a little confusion, so think about
754 if (((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetSessionUserId())
756 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
757 errmsg("session user may not be renamed")));
759 /* make sure the new name doesn't exist */
760 if (SearchSysCacheExists(SHADOWNAME,
761 CStringGetDatum(newname),
764 (errcode(ERRCODE_DUPLICATE_OBJECT),
765 errmsg("user \"%s\" already exists", newname)));
767 /* must be superuser */
770 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
771 errmsg("must be superuser to rename users")));
773 for (i = 0; i < Natts_pg_shadow; i++)
776 repl_repl[Anum_pg_shadow_usename - 1] = 'r';
777 repl_val[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
778 CStringGetDatum(newname));
779 repl_null[Anum_pg_shadow_usename - 1] = ' ';
781 datum = heap_getattr(oldtuple, Anum_pg_shadow_passwd, dsc, &isnull);
783 if (!isnull && isMD5(DatumGetCString(DirectFunctionCall1(textout, datum))))
785 /* MD5 uses the username as salt, so just clear it on a rename */
786 repl_repl[Anum_pg_shadow_passwd - 1] = 'r';
787 repl_null[Anum_pg_shadow_passwd - 1] = 'n';
790 (errmsg("MD5 password cleared because of user rename")));
793 newtuple = heap_modifytuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
794 simple_heap_update(rel, &oldtuple->t_self, newtuple);
796 CatalogUpdateIndexes(rel, newtuple);
798 ReleaseSysCache(oldtuple);
799 heap_close(rel, NoLock);
801 user_file_update_needed();
806 * CheckPgUserAclNotNull
808 * check to see if there is an ACL on pg_shadow
811 CheckPgUserAclNotNull(void)
815 htup = SearchSysCache(RELOID,
816 ObjectIdGetDatum(RelOid_pg_shadow),
818 if (!HeapTupleIsValid(htup)) /* should not happen, we hope */
819 elog(ERROR, "cache lookup failed for relation %u", RelOid_pg_shadow);
821 if (heap_attisnull(htup, Anum_pg_class_relacl))
823 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
824 errmsg("before using passwords you must revoke privileges on %s",
826 errdetail("This restriction is to prevent unprivileged users from reading the passwords."),
827 errhint("Try REVOKE ALL ON \"%s\" FROM PUBLIC.",
828 ShadowRelationName)));
830 ReleaseSysCache(htup);
838 CreateGroup(CreateGroupStmt *stmt)
840 Relation pg_group_rel;
843 TupleDesc pg_group_dsc;
844 bool group_exists = false,
845 sysid_exists = false,
848 Datum new_record[Natts_pg_group];
849 char new_record_nulls[Natts_pg_group];
855 List *userElts = NIL;
856 DefElem *dsysid = NULL;
857 DefElem *duserElts = NULL;
859 foreach(option, stmt->options)
861 DefElem *defel = (DefElem *) lfirst(option);
863 if (strcmp(defel->defname, "sysid") == 0)
867 (errcode(ERRCODE_SYNTAX_ERROR),
868 errmsg("conflicting or redundant options")));
871 else if (strcmp(defel->defname, "userElts") == 0)
875 (errcode(ERRCODE_SYNTAX_ERROR),
876 errmsg("conflicting or redundant options")));
880 elog(ERROR, "option \"%s\" not recognized",
886 sysid = intVal(dsysid->arg);
889 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
890 errmsg("group ID must be positive")));
895 userElts = (List *) duserElts->arg;
898 * Make sure the user can do this.
902 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
903 errmsg("must be superuser to create groups")));
905 if (strcmp(stmt->name, "public") == 0)
907 (errcode(ERRCODE_RESERVED_NAME),
908 errmsg("group name \"%s\" is reserved",
912 * Scan the pg_group relation to be certain the group or id doesn't
913 * already exist. Note we secure exclusive lock, because we also need
914 * to be sure of what the next grosysid should be, and we need to
915 * protect our eventual update of the flat group file.
917 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
918 pg_group_dsc = RelationGetDescr(pg_group_rel);
920 scan = heap_beginscan(pg_group_rel, SnapshotNow, 0, NULL);
921 max_id = 99; /* start auto-assigned ids at 100 */
922 while (!group_exists && !sysid_exists &&
923 (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
925 Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
928 group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
930 this_sysid = group_form->grosysid;
931 if (havesysid) /* customized id wanted */
932 sysid_exists = (this_sysid == sysid);
936 if (this_sysid > max_id)
944 (errcode(ERRCODE_DUPLICATE_OBJECT),
945 errmsg("group \"%s\" already exists",
949 (errcode(ERRCODE_DUPLICATE_OBJECT),
950 errmsg("group ID %d is already assigned", sysid)));
952 /* If no sysid given, use max existing id + 1 */
957 * Translate the given user names to ids
959 foreach(item, userElts)
961 const char *groupuser = strVal(lfirst(item));
962 int32 userid = get_usesysid(groupuser);
964 if (!list_member_int(newlist, userid))
965 newlist = lappend_int(newlist, userid);
968 /* build an array to insert */
970 grolist = IdListToArray(newlist);
975 * Form a tuple to insert
977 new_record[Anum_pg_group_groname - 1] =
978 DirectFunctionCall1(namein, CStringGetDatum(stmt->name));
979 new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid);
980 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist);
982 new_record_nulls[Anum_pg_group_groname - 1] = ' ';
983 new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
984 new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
986 tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
989 * Insert a new record in the pg_group table
991 simple_heap_insert(pg_group_rel, tuple);
994 CatalogUpdateIndexes(pg_group_rel, tuple);
997 * Now we can clean up; but keep lock until commit (to avoid possible
998 * deadlock when commit code tries to acquire lock).
1000 heap_close(pg_group_rel, NoLock);
1003 * Set flag to update flat group file at commit.
1005 group_file_update_needed();
1013 AlterGroup(AlterGroupStmt *stmt, const char *tag)
1015 Relation pg_group_rel;
1016 TupleDesc pg_group_dsc;
1017 HeapTuple group_tuple;
1025 * Make sure the user can do this.
1029 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1030 errmsg("must be superuser to alter groups")));
1033 * Secure exclusive lock to protect our update of the flat group file.
1035 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1036 pg_group_dsc = RelationGetDescr(pg_group_rel);
1039 * Fetch existing tuple for group.
1041 group_tuple = SearchSysCache(GRONAME,
1042 PointerGetDatum(stmt->name),
1044 if (!HeapTupleIsValid(group_tuple))
1046 (errcode(ERRCODE_UNDEFINED_OBJECT),
1047 errmsg("group \"%s\" does not exist", stmt->name)));
1049 /* Fetch old group membership. */
1050 datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
1051 pg_group_dsc, &null);
1052 oldarray = null ? NULL : DatumGetIdListP(datum);
1054 /* initialize list with old array contents */
1055 newlist = IdArrayToList(oldarray);
1058 * Now decide what to do.
1060 AssertState(stmt->action == +1 || stmt->action == -1);
1062 if (stmt->action == +1) /* add users, might also be invoked by
1066 * convert the to be added usernames to sysids and add them to the
1069 foreach(item, stmt->listUsers)
1073 if (strcmp(tag, "ALTER GROUP") == 0)
1075 /* Get the uid of the proposed user to add. */
1076 sysid = get_usesysid(strVal(lfirst(item)));
1078 else if (strcmp(tag, "CREATE USER") == 0)
1081 * in this case we already know the uid and it wouldn't be
1082 * in the cache anyway yet
1084 sysid = intVal(lfirst(item));
1088 elog(ERROR, "unexpected tag: \"%s\"", tag);
1089 sysid = 0; /* keep compiler quiet */
1092 if (!list_member_int(newlist, sysid))
1093 newlist = lappend_int(newlist, sysid);
1097 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1098 } /* endif alter group add user */
1100 else if (stmt->action == -1) /* drop users from group */
1102 bool is_dropuser = strcmp(tag, "DROP USER") == 0;
1108 (errcode(ERRCODE_WARNING),
1109 errmsg("group \"%s\" does not have any members",
1115 * convert the to be dropped usernames to sysids and remove
1116 * them from the list
1118 foreach(item, stmt->listUsers)
1124 /* Get the uid of the proposed user to drop. */
1125 sysid = get_usesysid(strVal(lfirst(item)));
1129 /* for dropuser we already know the uid */
1130 sysid = intVal(lfirst(item));
1132 if (list_member_int(newlist, sysid))
1133 newlist = list_delete_int(newlist, sysid);
1134 else if (!is_dropuser)
1136 (errcode(ERRCODE_WARNING),
1137 errmsg("user \"%s\" is not in group \"%s\"",
1138 strVal(lfirst(item)), stmt->name)));
1142 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1143 } /* endif group not null */
1144 } /* endif alter group drop user */
1146 ReleaseSysCache(group_tuple);
1149 * Now we can clean up; but keep lock until commit (to avoid possible
1150 * deadlock when commit code tries to acquire lock).
1152 heap_close(pg_group_rel, NoLock);
1155 * Set flag to update flat group file at commit.
1157 group_file_update_needed();
1161 * Subroutine for AlterGroup: given a pg_group tuple and a desired new
1162 * membership (expressed as an integer list), form and write an updated tuple.
1163 * The pg_group relation must be open and locked already.
1166 UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
1170 Datum new_record[Natts_pg_group];
1171 char new_record_nulls[Natts_pg_group];
1172 char new_record_repl[Natts_pg_group];
1175 newarray = IdListToArray(members);
1178 * Form an updated tuple with the new array and write it back.
1180 MemSet(new_record, 0, sizeof(new_record));
1181 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1182 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1184 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1185 new_record_repl[Anum_pg_group_grolist - 1] = 'r';
1187 tuple = heap_modifytuple(group_tuple, RelationGetDescr(group_rel),
1188 new_record, new_record_nulls, new_record_repl);
1190 simple_heap_update(group_rel, &group_tuple->t_self, tuple);
1192 /* Update indexes */
1193 CatalogUpdateIndexes(group_rel, tuple);
1198 * Convert an integer list of sysids to an array.
1201 IdListToArray(List *members)
1203 int nmembers = list_length(members);
1208 newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
1209 newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
1210 newarray->flags = 0;
1211 newarray->elemtype = INT4OID;
1212 ARR_NDIM(newarray) = 1; /* one dimensional array */
1213 ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */
1214 ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */
1216 foreach(item, members)
1217 ((int *) ARR_DATA_PTR(newarray))[i++] = lfirst_int(item);
1223 * Convert an array of sysids to an integer list.
1226 IdArrayToList(IdList *oldarray)
1228 List *newlist = NIL;
1232 if (oldarray == NULL)
1235 Assert(ARR_NDIM(oldarray) == 1);
1236 Assert(ARR_ELEMTYPE(oldarray) == INT4OID);
1238 hibound = ARR_DIMS(oldarray)[0];
1240 for (i = 0; i < hibound; i++)
1244 sysid = ((int32 *) ARR_DATA_PTR(oldarray))[i];
1245 /* filter out any duplicates --- probably a waste of time */
1246 if (!list_member_int(newlist, sysid))
1247 newlist = lappend_int(newlist, sysid);
1258 DropGroup(DropGroupStmt *stmt)
1260 Relation pg_group_rel;
1264 * Make sure the user can do this.
1268 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1269 errmsg("must be superuser to drop groups")));
1272 * Secure exclusive lock to protect our update of the flat group file.
1274 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1276 /* Find and delete the group. */
1278 tuple = SearchSysCacheCopy(GRONAME,
1279 PointerGetDatum(stmt->name),
1281 if (!HeapTupleIsValid(tuple))
1283 (errcode(ERRCODE_UNDEFINED_OBJECT),
1284 errmsg("group \"%s\" does not exist", stmt->name)));
1286 simple_heap_delete(pg_group_rel, &tuple->t_self);
1289 * Now we can clean up; but keep lock until commit (to avoid possible
1290 * deadlock when commit code tries to acquire lock).
1292 heap_close(pg_group_rel, NoLock);
1295 * Set flag to update flat group file at commit.
1297 group_file_update_needed();
1305 RenameGroup(const char *oldname, const char *newname)
1310 /* ExclusiveLock because we need to update the flat group file */
1311 rel = heap_openr(GroupRelationName, ExclusiveLock);
1313 tup = SearchSysCacheCopy(GRONAME,
1314 CStringGetDatum(oldname),
1316 if (!HeapTupleIsValid(tup))
1318 (errcode(ERRCODE_UNDEFINED_OBJECT),
1319 errmsg("group \"%s\" does not exist", oldname)));
1321 /* make sure the new name doesn't exist */
1322 if (SearchSysCacheExists(GRONAME,
1323 CStringGetDatum(newname),
1326 (errcode(ERRCODE_DUPLICATE_OBJECT),
1327 errmsg("group \"%s\" already exists", newname)));
1329 /* must be superuser */
1332 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1333 errmsg("must be superuser to rename groups")));
1336 namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
1337 simple_heap_update(rel, &tup->t_self, tup);
1338 CatalogUpdateIndexes(rel, tup);
1340 heap_close(rel, NoLock);
1341 heap_freetuple(tup);
1343 group_file_update_needed();