1 /*-------------------------------------------------------------------------
4 * Commands for manipulating users and groups.
6 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
9 * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.101 2002/05/17 01:19:17 tgl Exp $
11 *-------------------------------------------------------------------------
15 #include <sys/types.h>
21 #include "access/heapam.h"
22 #include "catalog/catname.h"
23 #include "catalog/pg_database.h"
24 #include "catalog/pg_shadow.h"
25 #include "catalog/pg_group.h"
26 #include "catalog/indexing.h"
27 #include "commands/user.h"
28 #include "libpq/crypt.h"
29 #include "miscadmin.h"
30 #include "storage/pmsignal.h"
31 #include "utils/acl.h"
32 #include "utils/array.h"
33 #include "utils/builtins.h"
34 #include "utils/fmgroids.h"
35 #include "utils/guc.h"
36 #include "utils/lsyscache.h"
37 #include "utils/syscache.h"
40 extern bool Password_encryption;
42 static void CheckPgUserAclNotNull(void);
43 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
45 static IdList *IdListToArray(List *members);
46 static List *IdArrayToList(IdList *oldarray);
52 * Outputs string in quotes, with double-quotes duplicated.
53 * We could use quote_ident(), but that expects varlena.
55 static void fputs_quote(char *str, FILE *fp)
71 * group_getfilename --- get full pathname of group file
73 * Note that result string is palloc'd, and should be freed by the caller.
76 group_getfilename(void)
81 bufsize = strlen(DataDir) + strlen("/global/") +
82 strlen(USER_GROUP_FILE) + 1;
83 pfnam = (char *) palloc(bufsize);
84 snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_GROUP_FILE);
92 * Get full pathname of password file.
93 * Note that result string is palloc'd, and should be freed by the caller.
96 user_getfilename(void)
101 bufsize = strlen(DataDir) + strlen("/global/") +
102 strlen(PWD_FILE) + 1;
103 pfnam = (char *) palloc(bufsize);
104 snprintf(pfnam, bufsize, "%s/global/%s", DataDir, PWD_FILE);
112 * write_group_file for trigger update_pg_pwd_and_pg_group
115 write_group_file(Relation urel, Relation grel)
124 TupleDesc dsc = RelationGetDescr(grel);
127 * Create a temporary filename to be renamed later. This prevents the
128 * backend from clobbering the pg_group file while the postmaster might
129 * be reading from it.
131 filename = group_getfilename();
132 bufsize = strlen(filename) + 12;
133 tempname = (char *) palloc(bufsize);
135 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
136 oumask = umask((mode_t) 077);
137 fp = AllocateFile(tempname, "w");
140 elog(ERROR, "write_group_file: unable to write %s: %m", tempname);
143 scan = heap_beginscan(grel, false, SnapshotSelf, 0, NULL);
144 while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
146 Datum datum, grolist_datum;
154 bool first_user = true;
156 datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull);
157 /* ignore NULL groupnames --- shouldn't happen */
160 groname = NameStr(*DatumGetName(datum));
163 * Check for illegal characters in the group name.
165 i = strcspn(groname, "\n");
166 if (groname[i] != '\0')
168 elog(LOG, "Invalid group name '%s'", groname);
172 grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull);
173 /* Ignore NULL group lists */
177 /* be sure the IdList is not toasted */
178 grolist_p = DatumGetIdListP(grolist_datum);
181 num = IDLIST_NUM(grolist_p);
182 aidp = IDLIST_DAT(grolist_p);
183 for (i = 0; i < num; ++i)
185 tuple = SearchSysCache(SHADOWSYSID,
186 PointerGetDatum(aidp[i]),
188 if (HeapTupleIsValid(tuple))
190 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
193 * Check for illegal characters in the user name.
195 j = strcspn(usename, "\n");
196 if (usename[j] != '\0')
198 elog(LOG, "Invalid user name '%s'", usename);
203 * "dbname" "user1" "user2" "user3"
207 fputs_quote(groname, fp);
214 fputs_quote(usename, fp);
216 ReleaseSysCache(tuple);
221 /* if IdList was toasted, free detoasted copy */
222 if ((Pointer) grolist_p != DatumGetPointer(grolist_datum))
229 elog(ERROR, "%s: %m", tempname);
233 * Rename the temp file to its final name, deleting the old pg_pwd. We
234 * expect that rename(2) is an atomic action.
236 if (rename(tempname, filename))
237 elog(ERROR, "rename %s to %s: %m", tempname, filename);
239 pfree((void *) tempname);
240 pfree((void *) filename);
246 * write_password_file for trigger update_pg_pwd_and_pg_group
248 * copy the modified contents of pg_shadow to a file used by the postmaster
249 * for user authentication. The file is stored as $PGDATA/global/pg_pwd.
251 * This function set is both a trigger function for direct updates to pg_shadow
252 * as well as being called directly from create/alter/drop user.
254 * We raise an error to force transaction rollback if we detect an illegal
255 * username or password --- illegal being defined as values that would
256 * mess up the pg_pwd parser.
259 write_user_file(Relation urel)
268 TupleDesc dsc = RelationGetDescr(urel);
271 * Create a temporary filename to be renamed later. This prevents the
272 * backend from clobbering the pg_pwd file while the postmaster might
273 * be reading from it.
275 filename = user_getfilename();
276 bufsize = strlen(filename) + 12;
277 tempname = (char *) palloc(bufsize);
279 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
280 oumask = umask((mode_t) 077);
281 fp = AllocateFile(tempname, "w");
284 elog(ERROR, "write_password_file: unable to write %s: %m", tempname);
287 scan = heap_beginscan(urel, false, SnapshotSelf, 0, NULL);
288 while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
297 datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull);
298 /* ignore NULL usernames (shouldn't happen) */
301 usename = NameStr(*DatumGetName(datum));
303 datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull);
306 * It can be argued that people having a null password shouldn't
307 * be allowed to connect under password authentication, because
308 * they need to have a password set up first. If you think
309 * assuming an empty password in that case is better, change this
310 * logic to look something like the code for valuntil.
315 passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
317 datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull);
319 valuntil = pstrdup("");
321 valuntil = DatumGetCString(DirectFunctionCall1(nabstimeout, datum));
324 * Check for illegal characters in the username and password.
326 i = strcspn(usename, "\n");
327 if (usename[i] != '\0')
328 elog(ERROR, "Invalid user name '%s'", usename);
329 i = strcspn(passwd, "\n");
330 if (passwd[i] != '\0')
331 elog(ERROR, "Invalid user password '%s'", passwd);
334 * The extra columns we emit here are not really necessary. To
335 * remove them, the parser in backend/libpq/crypt.c would need to
338 fputs_quote(usename, fp);
340 fputs_quote(passwd, fp);
342 fputs_quote(valuntil, fp);
352 elog(ERROR, "%s: %m", tempname);
356 * Rename the temp file to its final name, deleting the old pg_pwd. We
357 * expect that rename(2) is an atomic action.
359 if (rename(tempname, filename))
360 elog(ERROR, "rename %s to %s: %m", tempname, filename);
362 pfree((void *) tempname);
363 pfree((void *) filename);
368 /* This is the wrapper for triggers. */
370 update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
373 * ExclusiveLock ensures no one modifies pg_shadow while we read it,
374 * and that only one backend rewrites the flat file at a time. It's
375 * OK to allow normal reads of pg_shadow in parallel, however.
377 Relation urel = heap_openr(ShadowRelationName, ExclusiveLock);
378 Relation grel = heap_openr(GroupRelationName, ExclusiveLock);
380 write_user_file(urel);
381 write_group_file(urel, grel);
382 /* OK to release lock, since we did not modify the relation */
383 heap_close(grel, ExclusiveLock);
384 heap_close(urel, ExclusiveLock);
387 * Signal the postmaster to reload its password & group-file cache.
389 SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
391 return PointerGetDatum(NULL);
400 CreateUser(CreateUserStmt *stmt)
402 Relation pg_shadow_rel;
403 TupleDesc pg_shadow_dsc;
406 Datum new_record[Natts_pg_shadow];
407 char new_record_nulls[Natts_pg_shadow];
408 bool user_exists = false,
409 sysid_exists = false,
414 char *password = NULL; /* PostgreSQL user password */
415 bool encrypt_password = Password_encryption; /* encrypt password? */
416 char encrypted_password[MD5_PASSWD_LEN + 1];
417 int sysid = 0; /* PgSQL system id (valid if havesysid) */
418 bool createdb = false; /* Can the user create databases? */
419 bool createuser = false; /* Can this user create users? */
420 List *groupElts = NIL; /* The groups the user is a member of */
421 char *validUntil = NULL; /* The time the login is valid
423 DefElem *dpassword = NULL;
424 DefElem *dsysid = NULL;
425 DefElem *dcreatedb = NULL;
426 DefElem *dcreateuser = NULL;
427 DefElem *dgroupElts = NULL;
428 DefElem *dvalidUntil = NULL;
430 /* Extract options from the statement node tree */
431 foreach(option, stmt->options)
433 DefElem *defel = (DefElem *) lfirst(option);
435 if (strcmp(defel->defname, "password") == 0 ||
436 strcmp(defel->defname, "encryptedPassword") == 0 ||
437 strcmp(defel->defname, "unencryptedPassword") == 0)
440 elog(ERROR, "CREATE USER: conflicting options");
442 if (strcmp(defel->defname, "encryptedPassword") == 0)
443 encrypt_password = true;
444 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
445 encrypt_password = false;
447 else if (strcmp(defel->defname, "sysid") == 0)
450 elog(ERROR, "CREATE USER: conflicting options");
453 else if (strcmp(defel->defname, "createdb") == 0)
456 elog(ERROR, "CREATE USER: conflicting options");
459 else if (strcmp(defel->defname, "createuser") == 0)
462 elog(ERROR, "CREATE USER: conflicting options");
465 else if (strcmp(defel->defname, "groupElts") == 0)
468 elog(ERROR, "CREATE USER: conflicting options");
471 else if (strcmp(defel->defname, "validUntil") == 0)
474 elog(ERROR, "CREATE USER: conflicting options");
478 elog(ERROR, "CREATE USER: option \"%s\" not recognized",
483 createdb = intVal(dcreatedb->arg) != 0;
485 createuser = intVal(dcreateuser->arg) != 0;
488 sysid = intVal(dsysid->arg);
490 elog(ERROR, "user id must be positive");
494 validUntil = strVal(dvalidUntil->arg);
496 password = strVal(dpassword->arg);
498 groupElts = (List *) dgroupElts->arg;
500 /* Check some permissions first */
502 CheckPgUserAclNotNull();
505 elog(ERROR, "CREATE USER: permission denied");
507 if (strcmp(stmt->user, "public") == 0)
508 elog(ERROR, "CREATE USER: user name \"%s\" is reserved",
512 * Scan the pg_shadow relation to be certain the user or id doesn't
513 * already exist. Note we secure exclusive lock, because we also need
514 * to be sure of what the next usesysid should be, and we need to
515 * protect our update of the flat password file.
517 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
518 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
520 scan = heap_beginscan(pg_shadow_rel, false, SnapshotNow, 0, NULL);
521 max_id = 99; /* start auto-assigned ids at 100 */
522 while (!user_exists && !sysid_exists &&
523 HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
525 Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
528 user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
530 this_sysid = shadow_form->usesysid;
531 if (havesysid) /* customized id wanted */
532 sysid_exists = (this_sysid == sysid);
536 if (this_sysid > max_id)
543 elog(ERROR, "CREATE USER: user name \"%s\" already exists",
546 elog(ERROR, "CREATE USER: sysid %d is already assigned", sysid);
548 /* If no sysid given, use max existing id + 1 */
553 * Build a tuple to insert
555 MemSet(new_record, 0, sizeof(new_record));
556 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
558 new_record[Anum_pg_shadow_usename - 1] =
559 DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
560 new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
561 AssertState(BoolIsValid(createdb));
562 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
563 new_record[Anum_pg_shadow_usetrace - 1] = BoolGetDatum(false);
564 AssertState(BoolIsValid(createuser));
565 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
566 /* superuser gets catupd right by default */
567 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
571 if (!encrypt_password || isMD5(password))
572 new_record[Anum_pg_shadow_passwd - 1] =
573 DirectFunctionCall1(textin, CStringGetDatum(password));
576 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
578 elog(ERROR, "CREATE USER: password encryption failed");
579 new_record[Anum_pg_shadow_passwd - 1] =
580 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
584 new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
587 new_record[Anum_pg_shadow_valuntil - 1] =
588 DirectFunctionCall1(nabstimein, CStringGetDatum(validUntil));
590 new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
592 new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
594 tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
597 * Insert new record in the pg_shadow table
599 heap_insert(pg_shadow_rel, tuple);
604 if (RelationGetForm(pg_shadow_rel)->relhasindex)
606 Relation idescs[Num_pg_shadow_indices];
608 CatalogOpenIndices(Num_pg_shadow_indices,
609 Name_pg_shadow_indices, idescs);
610 CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
612 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
616 * Add the user to the groups specified. We'll just call the below
617 * AlterGroup for this.
619 foreach(item, groupElts)
623 ags.name = strVal(lfirst(item)); /* the group name to add
626 ags.listUsers = makeList1(makeInteger(sysid));
627 AlterGroup(&ags, "CREATE USER");
631 * Now we can clean up; but keep lock until commit.
633 heap_close(pg_shadow_rel, NoLock);
636 * Write the updated pg_shadow and pg_group data to the flat file.
638 update_pg_pwd_and_pg_group(NULL);
647 AlterUser(AlterUserStmt *stmt)
649 Datum new_record[Natts_pg_shadow];
650 char new_record_nulls[Natts_pg_shadow];
651 char new_record_repl[Natts_pg_shadow];
652 Relation pg_shadow_rel;
653 TupleDesc pg_shadow_dsc;
657 char *password = NULL; /* PostgreSQL user password */
658 bool encrypt_password = Password_encryption; /* encrypt password? */
659 char encrypted_password[MD5_PASSWD_LEN + 1];
660 int createdb = -1; /* Can the user create databases? */
661 int createuser = -1; /* Can this user create users? */
662 char *validUntil = NULL; /* The time the login is valid
664 DefElem *dpassword = NULL;
665 DefElem *dcreatedb = NULL;
666 DefElem *dcreateuser = NULL;
667 DefElem *dvalidUntil = NULL;
669 /* Extract options from the statement node tree */
670 foreach(option, stmt->options)
672 DefElem *defel = (DefElem *) lfirst(option);
674 if (strcmp(defel->defname, "password") == 0 ||
675 strcmp(defel->defname, "encryptedPassword") == 0 ||
676 strcmp(defel->defname, "unencryptedPassword") == 0)
679 elog(ERROR, "ALTER USER: conflicting options");
681 if (strcmp(defel->defname, "encryptedPassword") == 0)
682 encrypt_password = true;
683 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
684 encrypt_password = false;
686 else if (strcmp(defel->defname, "createdb") == 0)
689 elog(ERROR, "ALTER USER: conflicting options");
692 else if (strcmp(defel->defname, "createuser") == 0)
695 elog(ERROR, "ALTER USER: conflicting options");
698 else if (strcmp(defel->defname, "validUntil") == 0)
701 elog(ERROR, "ALTER USER: conflicting options");
705 elog(ERROR, "ALTER USER: option \"%s\" not recognized",
710 createdb = intVal(dcreatedb->arg);
712 createuser = intVal(dcreateuser->arg);
714 validUntil = strVal(dvalidUntil->arg);
716 password = strVal(dpassword->arg);
719 CheckPgUserAclNotNull();
721 /* must be superuser or just want to change your own password */
727 strcmp(GetUserName(GetUserId()), stmt->user) == 0))
728 elog(ERROR, "ALTER USER: permission denied");
730 /* changes to the flat password file cannot be rolled back */
731 if (IsTransactionBlock() && password)
732 elog(NOTICE, "ALTER USER: password changes cannot be rolled back");
735 * Scan the pg_shadow relation to be certain the user exists. Note we
736 * secure exclusive lock to protect our update of the flat password
739 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
740 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
742 tuple = SearchSysCache(SHADOWNAME,
743 PointerGetDatum(stmt->user),
745 if (!HeapTupleIsValid(tuple))
746 elog(ERROR, "ALTER USER: user \"%s\" does not exist", stmt->user);
749 * Build an updated tuple, perusing the information just obtained
751 MemSet(new_record, 0, sizeof(new_record));
752 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
753 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
755 new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
756 CStringGetDatum(stmt->user));
757 new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
762 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
763 new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
767 * createuser (superuser) and catupd
769 * XXX It's rather unclear how to handle catupd. It's probably best to
770 * keep it equal to the superuser status, otherwise you could end up
771 * with a situation where no existing superuser can alter the
772 * catalogs, including pg_shadow!
776 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
777 new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
779 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
780 new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
786 if (!encrypt_password || isMD5(password))
787 new_record[Anum_pg_shadow_passwd - 1] =
788 DirectFunctionCall1(textin, CStringGetDatum(password));
791 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
793 elog(ERROR, "CREATE USER: password encryption failed");
794 new_record[Anum_pg_shadow_passwd - 1] =
795 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
797 new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
803 new_record[Anum_pg_shadow_valuntil - 1] =
804 DirectFunctionCall1(nabstimein, CStringGetDatum(validUntil));
805 new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
808 new_tuple = heap_modifytuple(tuple, pg_shadow_rel, new_record,
809 new_record_nulls, new_record_repl);
810 simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
813 if (RelationGetForm(pg_shadow_rel)->relhasindex)
815 Relation idescs[Num_pg_shadow_indices];
817 CatalogOpenIndices(Num_pg_shadow_indices,
818 Name_pg_shadow_indices, idescs);
819 CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
821 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
824 ReleaseSysCache(tuple);
825 heap_freetuple(new_tuple);
828 * Now we can clean up.
830 heap_close(pg_shadow_rel, NoLock);
833 * Write the updated pg_shadow and pg_group data to the flat file.
835 update_pg_pwd_and_pg_group(NULL);
843 AlterUserSet(AlterUserSetStmt *stmt)
849 Datum repl_val[Natts_pg_shadow];
850 char repl_null[Natts_pg_shadow];
851 char repl_repl[Natts_pg_shadow];
854 valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
857 * RowExclusiveLock is sufficient, because we don't need to update
858 * the flat password file.
860 rel = heap_openr(ShadowRelationName, RowExclusiveLock);
861 oldtuple = SearchSysCache(SHADOWNAME,
862 PointerGetDatum(stmt->user),
864 if (!HeapTupleIsValid(oldtuple))
865 elog(ERROR, "user \"%s\" does not exist", stmt->user);
868 || ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
869 elog(ERROR, "permission denied");
871 for (i = 0; i < Natts_pg_shadow; i++)
874 repl_repl[Anum_pg_shadow_useconfig-1] = 'r';
875 if (strcmp(stmt->variable, "all")==0 && valuestr == NULL)
877 repl_null[Anum_pg_shadow_useconfig-1] = 'n';
884 repl_null[Anum_pg_shadow_useconfig-1] = ' ';
886 datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
887 Anum_pg_shadow_useconfig, &isnull);
889 array = isnull ? ((ArrayType *) NULL) : DatumGetArrayTypeP(datum);
892 array = GUCArrayAdd(array, stmt->variable, valuestr);
894 array = GUCArrayDelete(array, stmt->variable);
896 repl_val[Anum_pg_shadow_useconfig-1] = PointerGetDatum(array);
899 newtuple = heap_modifytuple(oldtuple, rel, repl_val, repl_null, repl_repl);
900 simple_heap_update(rel, &oldtuple->t_self, newtuple);
903 Relation idescs[Num_pg_shadow_indices];
905 CatalogOpenIndices(Num_pg_shadow_indices, Name_pg_shadow_indices, idescs);
906 CatalogIndexInsert(idescs, Num_pg_shadow_indices, rel, newtuple);
907 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
910 ReleaseSysCache(oldtuple);
911 heap_close(rel, RowExclusiveLock);
920 DropUser(DropUserStmt *stmt)
922 Relation pg_shadow_rel;
923 TupleDesc pg_shadow_dsc;
927 elog(ERROR, "DROP USER: permission denied");
929 if (IsTransactionBlock())
930 elog(NOTICE, "DROP USER cannot be rolled back completely");
933 * Scan the pg_shadow relation to find the usesysid of the user to be
934 * deleted. Note we secure exclusive lock, because we need to protect
935 * our update of the flat password file.
937 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
938 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
940 foreach(item, stmt->users)
942 const char *user = strVal(lfirst(item));
951 tuple = SearchSysCache(SHADOWNAME,
952 PointerGetDatum(user),
954 if (!HeapTupleIsValid(tuple))
955 elog(ERROR, "DROP USER: user \"%s\" does not exist%s", user,
956 (length(stmt->users) > 1) ? " (no users removed)" : "");
958 usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
960 if (usesysid == GetUserId())
961 elog(ERROR, "current user cannot be dropped");
962 if (usesysid == GetSessionUserId())
963 elog(ERROR, "session user cannot be dropped");
966 * Check if user still owns a database. If so, error out.
968 * (It used to be that this function would drop the database
969 * automatically. This is not only very dangerous for people that
970 * don't read the manual, it doesn't seem to be the behaviour one
971 * would expect either.) -- petere 2000/01/14)
973 pg_rel = heap_openr(DatabaseRelationName, AccessShareLock);
974 pg_dsc = RelationGetDescr(pg_rel);
976 ScanKeyEntryInitialize(&scankey, 0x0,
977 Anum_pg_database_datdba, F_INT4EQ,
978 Int32GetDatum(usesysid));
980 scan = heap_beginscan(pg_rel, false, SnapshotNow, 1, &scankey);
982 if (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
986 dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
987 elog(ERROR, "DROP USER: user \"%s\" owns database \"%s\", cannot be removed%s",
989 (length(stmt->users) > 1) ? " (no users removed)" : "");
993 heap_close(pg_rel, AccessShareLock);
996 * Somehow we'd have to check for tables, views, etc. owned by the
997 * user as well, but those could be spread out over all sorts of
998 * databases which we don't have access to (easily).
1002 * Remove the user from the pg_shadow table
1004 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
1006 ReleaseSysCache(tuple);
1009 * Remove user from groups
1011 * try calling alter group drop user for every group
1013 pg_rel = heap_openr(GroupRelationName, ExclusiveLock);
1014 pg_dsc = RelationGetDescr(pg_rel);
1015 scan = heap_beginscan(pg_rel, false, SnapshotNow, 0, NULL);
1016 while (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
1020 /* the group name from which to try to drop the user: */
1021 ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
1023 ags.listUsers = makeList1(makeInteger(usesysid));
1024 AlterGroup(&ags, "DROP USER");
1027 heap_close(pg_rel, ExclusiveLock);
1030 * Advance command counter so that later iterations of this loop
1031 * will see the changes already made. This is essential if, for
1032 * example, we are trying to drop two users who are members of the
1033 * same group --- the AlterGroup for the second user had better
1034 * see the tuple updated from the first one.
1036 CommandCounterIncrement();
1040 * Now we can clean up.
1042 heap_close(pg_shadow_rel, NoLock);
1045 * Write the updated pg_shadow and pg_group data to the flat file.
1047 update_pg_pwd_and_pg_group(NULL);
1053 * CheckPgUserAclNotNull
1055 * check to see if there is an ACL on pg_shadow
1058 CheckPgUserAclNotNull(void)
1062 htup = SearchSysCache(RELOID,
1063 ObjectIdGetDatum(RelOid_pg_shadow),
1065 if (!HeapTupleIsValid(htup))
1066 elog(ERROR, "CheckPgUserAclNotNull: \"%s\" not found",
1067 ShadowRelationName);
1069 if (heap_attisnull(htup, Anum_pg_class_relacl))
1071 "To use passwords, you have to revoke permissions on %s "
1072 "so normal users cannot read the passwords. "
1073 "Try 'REVOKE ALL ON \"%s\" FROM PUBLIC'.",
1074 ShadowRelationName, ShadowRelationName);
1076 ReleaseSysCache(htup);
1085 CreateGroup(CreateGroupStmt *stmt)
1087 Relation pg_group_rel;
1090 TupleDesc pg_group_dsc;
1091 bool group_exists = false,
1092 sysid_exists = false,
1095 Datum new_record[Natts_pg_group];
1096 char new_record_nulls[Natts_pg_group];
1102 List *userElts = NIL;
1103 DefElem *dsysid = NULL;
1104 DefElem *duserElts = NULL;
1106 foreach(option, stmt->options)
1108 DefElem *defel = (DefElem *) lfirst(option);
1110 if (strcmp(defel->defname, "sysid") == 0)
1113 elog(ERROR, "CREATE GROUP: conflicting options");
1116 else if (strcmp(defel->defname, "userElts") == 0)
1119 elog(ERROR, "CREATE GROUP: conflicting options");
1123 elog(ERROR, "CREATE GROUP: option \"%s\" not recognized",
1129 sysid = intVal(dsysid->arg);
1131 elog(ERROR, "group id must be positive");
1136 userElts = (List *) duserElts->arg;
1139 * Make sure the user can do this.
1142 elog(ERROR, "CREATE GROUP: permission denied");
1144 if (strcmp(stmt->name, "public") == 0)
1145 elog(ERROR, "CREATE GROUP: group name \"%s\" is reserved",
1148 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1149 pg_group_dsc = RelationGetDescr(pg_group_rel);
1151 scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
1152 max_id = 99; /* start auto-assigned ids at 100 */
1153 while (!group_exists && !sysid_exists &&
1154 HeapTupleIsValid(tuple = heap_getnext(scan, false)))
1156 Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
1159 group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
1161 this_sysid = group_form->grosysid;
1162 if (havesysid) /* customized id wanted */
1163 sysid_exists = (this_sysid == sysid);
1167 if (this_sysid > max_id)
1168 max_id = this_sysid;
1174 elog(ERROR, "CREATE GROUP: group name \"%s\" already exists",
1177 elog(ERROR, "CREATE GROUP: group sysid %d is already assigned",
1184 * Translate the given user names to ids
1186 foreach(item, userElts)
1188 const char *groupuser = strVal(lfirst(item));
1189 int32 userid = get_usesysid(groupuser);
1191 if (!intMember(userid, newlist))
1192 newlist = lappendi(newlist, userid);
1195 /* build an array to insert */
1197 grolist = IdListToArray(newlist);
1202 * Form a tuple to insert
1204 new_record[Anum_pg_group_groname - 1] =
1205 DirectFunctionCall1(namein, CStringGetDatum(stmt->name));
1206 new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid);
1207 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist);
1209 new_record_nulls[Anum_pg_group_groname - 1] = ' ';
1210 new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
1211 new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
1213 tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
1216 * Insert a new record in the pg_group_table
1218 heap_insert(pg_group_rel, tuple);
1223 if (RelationGetForm(pg_group_rel)->relhasindex)
1225 Relation idescs[Num_pg_group_indices];
1227 CatalogOpenIndices(Num_pg_group_indices,
1228 Name_pg_group_indices, idescs);
1229 CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
1231 CatalogCloseIndices(Num_pg_group_indices, idescs);
1234 heap_close(pg_group_rel, NoLock);
1237 * Write the updated pg_shadow and pg_group data to the flat file.
1239 update_pg_pwd_and_pg_group(NULL);
1248 AlterGroup(AlterGroupStmt *stmt, const char *tag)
1250 Relation pg_group_rel;
1251 TupleDesc pg_group_dsc;
1252 HeapTuple group_tuple;
1260 * Make sure the user can do this.
1263 elog(ERROR, "%s: permission denied", tag);
1265 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1266 pg_group_dsc = RelationGetDescr(pg_group_rel);
1269 * Fetch existing tuple for group.
1271 group_tuple = SearchSysCache(GRONAME,
1272 PointerGetDatum(stmt->name),
1274 if (!HeapTupleIsValid(group_tuple))
1275 elog(ERROR, "%s: group \"%s\" does not exist", tag, stmt->name);
1277 /* Fetch old group membership. */
1278 datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
1279 pg_group_dsc, &null);
1280 oldarray = null ? ((IdList *) NULL) : DatumGetIdListP(datum);
1282 /* initialize list with old array contents */
1283 newlist = IdArrayToList(oldarray);
1286 * Now decide what to do.
1288 AssertState(stmt->action == +1 || stmt->action == -1);
1290 if (stmt->action == +1) /* add users, might also be invoked by
1294 * convert the to be added usernames to sysids and add them to
1297 foreach(item, stmt->listUsers)
1301 if (strcmp(tag, "ALTER GROUP") == 0)
1303 /* Get the uid of the proposed user to add. */
1304 sysid = get_usesysid(strVal(lfirst(item)));
1306 else if (strcmp(tag, "CREATE USER") == 0)
1309 * in this case we already know the uid and it wouldn't be
1310 * in the cache anyway yet
1312 sysid = intVal(lfirst(item));
1316 elog(ERROR, "AlterGroup: unknown tag %s", tag);
1317 sysid = 0; /* keep compiler quiet */
1320 if (!intMember(sysid, newlist))
1321 newlist = lappendi(newlist, sysid);
1324 * we silently assume here that this error will only come
1325 * up in a ALTER GROUP statement
1327 elog(WARNING, "%s: user \"%s\" is already in group \"%s\"",
1328 tag, strVal(lfirst(item)), stmt->name);
1332 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1333 } /* endif alter group add user */
1335 else if (stmt->action == -1) /* drop users from group */
1337 bool is_dropuser = strcmp(tag, "DROP USER") == 0;
1342 elog(WARNING, "ALTER GROUP: group \"%s\" does not have any members", stmt->name);
1347 * convert the to be dropped usernames to sysids and
1348 * remove them from the list
1350 foreach(item, stmt->listUsers)
1356 /* Get the uid of the proposed user to drop. */
1357 sysid = get_usesysid(strVal(lfirst(item)));
1361 /* for dropuser we already know the uid */
1362 sysid = intVal(lfirst(item));
1364 if (intMember(sysid, newlist))
1365 newlist = lremovei(sysid, newlist);
1366 else if (!is_dropuser)
1367 elog(WARNING, "ALTER GROUP: user \"%s\" is not in group \"%s\"", strVal(lfirst(item)), stmt->name);
1371 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1372 } /* endif group not null */
1373 } /* endif alter group drop user */
1375 ReleaseSysCache(group_tuple);
1378 * Write the updated pg_shadow and pg_group data to the flat files.
1380 heap_close(pg_group_rel, NoLock);
1383 * Write the updated pg_shadow and pg_group data to the flat file.
1385 update_pg_pwd_and_pg_group(NULL);
1389 * Subroutine for AlterGroup: given a pg_group tuple and a desired new
1390 * membership (expressed as an integer list), form and write an updated tuple.
1391 * The pg_group relation must be open and locked already.
1394 UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
1398 Datum new_record[Natts_pg_group];
1399 char new_record_nulls[Natts_pg_group];
1400 char new_record_repl[Natts_pg_group];
1403 newarray = IdListToArray(members);
1406 * Form an updated tuple with the new array and write it back.
1408 MemSet(new_record, 0, sizeof(new_record));
1409 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1410 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1412 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1413 new_record_repl[Anum_pg_group_grolist - 1] = 'r';
1415 tuple = heap_modifytuple(group_tuple, group_rel,
1416 new_record, new_record_nulls, new_record_repl);
1418 simple_heap_update(group_rel, &group_tuple->t_self, tuple);
1420 /* Update indexes */
1421 if (RelationGetForm(group_rel)->relhasindex)
1423 Relation idescs[Num_pg_group_indices];
1425 CatalogOpenIndices(Num_pg_group_indices,
1426 Name_pg_group_indices, idescs);
1427 CatalogIndexInsert(idescs, Num_pg_group_indices, group_rel,
1429 CatalogCloseIndices(Num_pg_group_indices, idescs);
1435 * Convert an integer list of sysids to an array.
1438 IdListToArray(List *members)
1440 int nmembers = length(members);
1445 newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
1446 newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
1447 newarray->flags = 0;
1448 ARR_NDIM(newarray) = 1; /* one dimensional array */
1449 ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */
1450 ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */
1452 foreach(item, members)
1454 ((int *) ARR_DATA_PTR(newarray))[i++] = lfirsti(item);
1461 * Convert an array of sysids to an integer list.
1464 IdArrayToList(IdList *oldarray)
1466 List *newlist = NIL;
1470 if (oldarray == NULL)
1473 Assert(ARR_NDIM(oldarray) == 1);
1475 hibound = ARR_DIMS(oldarray)[0];
1477 for (i = 0; i < hibound; i++)
1481 sysid = ((int *) ARR_DATA_PTR(oldarray))[i];
1482 /* filter out any duplicates --- probably a waste of time */
1483 if (!intMember(sysid, newlist))
1484 newlist = lappendi(newlist, sysid);
1495 DropGroup(DropGroupStmt *stmt)
1497 Relation pg_group_rel;
1501 * Make sure the user can do this.
1504 elog(ERROR, "DROP GROUP: permission denied");
1509 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1511 tuple = SearchSysCacheCopy(GRONAME,
1512 PointerGetDatum(stmt->name),
1514 if (!HeapTupleIsValid(tuple))
1515 elog(ERROR, "DROP GROUP: group \"%s\" does not exist", stmt->name);
1517 simple_heap_delete(pg_group_rel, &tuple->t_self);
1519 heap_close(pg_group_rel, NoLock);
1522 * Write the updated pg_shadow and pg_group data to the flat file.
1524 update_pg_pwd_and_pg_group(NULL);