1 /*-------------------------------------------------------------------------
4 * Commands for manipulating users and groups.
6 * Portions Copyright (c) 1996-2003, 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.138 2004/02/25 19:41:22 momjian Exp $
11 *-------------------------------------------------------------------------
20 #include "access/heapam.h"
21 #include "catalog/catname.h"
22 #include "catalog/indexing.h"
23 #include "catalog/pg_database.h"
24 #include "catalog/pg_group.h"
25 #include "catalog/pg_shadow.h"
26 #include "catalog/pg_type.h"
27 #include "commands/user.h"
28 #include "libpq/crypt.h"
29 #include "miscadmin.h"
30 #include "storage/fd.h"
31 #include "storage/pmsignal.h"
32 #include "utils/acl.h"
33 #include "utils/array.h"
34 #include "utils/builtins.h"
35 #include "utils/fmgroids.h"
36 #include "utils/guc.h"
37 #include "utils/lsyscache.h"
38 #include "utils/syscache.h"
41 #define PWD_FILE "pg_pwd"
42 #define USER_GROUP_FILE "pg_group"
45 extern bool Password_encryption;
47 static bool user_file_update_needed = false;
48 static bool group_file_update_needed = false;
51 static void CheckPgUserAclNotNull(void);
52 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
54 static IdList *IdListToArray(List *members);
55 static List *IdArrayToList(IdList *oldarray);
61 * Outputs string in quotes, with double-quotes duplicated.
62 * We could use quote_ident(), but that expects a TEXT argument.
65 fputs_quote(char *str, FILE *fp)
80 * group_getfilename --- get full pathname of group file
82 * Note that result string is palloc'd, and should be freed by the caller.
85 group_getfilename(void)
90 bufsize = strlen(DataDir) + strlen("/global/") +
91 strlen(USER_GROUP_FILE) + 1;
92 pfnam = (char *) palloc(bufsize);
93 snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_GROUP_FILE);
100 * Get full pathname of password file.
102 * Note that result string is palloc'd, and should be freed by the caller.
105 user_getfilename(void)
110 bufsize = strlen(DataDir) + strlen("/global/") +
111 strlen(PWD_FILE) + 1;
112 pfnam = (char *) palloc(bufsize);
113 snprintf(pfnam, bufsize, "%s/global/%s", DataDir, PWD_FILE);
120 * write_group_file: update the flat group file
123 write_group_file(Relation grel)
132 TupleDesc dsc = RelationGetDescr(grel);
135 * Create a temporary filename to be renamed later. This prevents the
136 * backend from clobbering the pg_group file while the postmaster
137 * might be reading from it.
139 filename = group_getfilename();
140 bufsize = strlen(filename) + 12;
141 tempname = (char *) palloc(bufsize);
142 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
143 #if defined(WIN32) || defined(__CYGWIN__)
144 filename = repalloc(filename, strlen(filename) + 1 + strlen(".new"));
145 strcat(filename, ".new");
148 oumask = umask((mode_t) 077);
149 fp = AllocateFile(tempname, "w");
153 (errcode_for_file_access(),
154 errmsg("could not write to temporary file \"%s\": %m", tempname)));
157 * Read pg_group and write the file. Note we use SnapshotSelf to
158 * ensure we see all effects of current transaction. (Perhaps could
159 * do a CommandCounterIncrement beforehand, instead?)
161 scan = heap_beginscan(grel, SnapshotSelf, 0, NULL);
162 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
174 bool first_user = true;
176 datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull);
177 /* ignore NULL groupnames --- shouldn't happen */
180 groname = NameStr(*DatumGetName(datum));
183 * Check for invalid characters in the group name.
185 i = strcspn(groname, "\n");
186 if (groname[i] != '\0')
189 (errmsg("invalid group name \"%s\"", groname)));
193 grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull);
194 /* Ignore NULL group lists */
198 /* be sure the IdList is not toasted */
199 grolist_p = DatumGetIdListP(grolist_datum);
202 num = IDLIST_NUM(grolist_p);
203 aidp = IDLIST_DAT(grolist_p);
204 for (i = 0; i < num; ++i)
206 tuple = SearchSysCache(SHADOWSYSID,
207 PointerGetDatum(aidp[i]),
209 if (HeapTupleIsValid(tuple))
211 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
214 * Check for illegal characters in the user name.
216 j = strcspn(usename, "\n");
217 if (usename[j] != '\0')
220 (errmsg("invalid user name \"%s\"", usename)));
225 * File format is: "dbname" "user1" "user2" "user3"
229 fputs_quote(groname, fp);
236 fputs_quote(usename, fp);
238 ReleaseSysCache(tuple);
243 /* if IdList was toasted, free detoasted copy */
244 if ((Pointer) grolist_p != DatumGetPointer(grolist_datum))
251 (errcode_for_file_access(),
252 errmsg("could not write to temporary file \"%s\": %m",
256 * Rename the temp file to its final name, deleting the old pg_pwd. We
257 * expect that rename(2) is an atomic action.
259 if (rename(tempname, filename))
261 (errcode_for_file_access(),
262 errmsg("could not rename file \"%s\" to \"%s\": %m",
263 tempname, filename)));
265 pfree((void *) tempname);
266 pfree((void *) filename);
271 * write_user_file: update the flat password file
274 write_user_file(Relation urel)
283 TupleDesc dsc = RelationGetDescr(urel);
286 * Create a temporary filename to be renamed later. This prevents the
287 * backend from clobbering the pg_pwd file while the postmaster might
288 * be reading from it.
290 filename = user_getfilename();
291 bufsize = strlen(filename) + 12;
292 tempname = (char *) palloc(bufsize);
293 snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
294 #if defined(WIN32) || defined(__CYGWIN__)
295 filename = repalloc(filename, strlen(filename) + 1 + strlen(".new"));
296 strcat(filename, ".new");
299 oumask = umask((mode_t) 077);
300 fp = AllocateFile(tempname, "w");
304 (errcode_for_file_access(),
305 errmsg("could not write to temporary file \"%s\": %m", tempname)));
308 * Read pg_shadow and write the file. Note we use SnapshotSelf to
309 * ensure we see all effects of current transaction. (Perhaps could
310 * do a CommandCounterIncrement beforehand, instead?)
312 scan = heap_beginscan(urel, SnapshotSelf, 0, NULL);
313 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
322 datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull);
323 /* ignore NULL usernames (shouldn't happen) */
326 usename = NameStr(*DatumGetName(datum));
328 datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull);
331 * It can be argued that people having a null password shouldn't
332 * be allowed to connect under password authentication, because
333 * they need to have a password set up first. If you think
334 * assuming an empty password in that case is better, change this
335 * logic to look something like the code for valuntil.
340 passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
342 datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull);
344 valuntil = pstrdup("");
346 valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum));
349 * Check for illegal characters in the username and password.
351 i = strcspn(usename, "\n");
352 if (usename[i] != '\0')
355 (errmsg("invalid user name \"%s\"", usename)));
358 i = strcspn(passwd, "\n");
359 if (passwd[i] != '\0')
362 (errmsg("invalid user password \"%s\"", passwd)));
367 * The extra columns we emit here are not really necessary. To
368 * remove them, the parser in backend/libpq/crypt.c would need to
371 fputs_quote(usename, fp);
373 fputs_quote(passwd, fp);
375 fputs_quote(valuntil, fp);
385 (errcode_for_file_access(),
386 errmsg("could not write to temporary file \"%s\": %m",
390 * Rename the temp file to its final name, deleting the old pg_pwd. We
391 * expect that rename(2) is an atomic action.
393 if (rename(tempname, filename))
395 (errcode_for_file_access(),
396 errmsg("could not rename file \"%s\" to \"%s\": %m",
397 tempname, filename)));
399 pfree((void *) tempname);
400 pfree((void *) filename);
405 * This trigger is fired whenever someone modifies pg_shadow or pg_group
406 * via general-purpose INSERT/UPDATE/DELETE commands.
408 * XXX should probably have two separate triggers.
411 update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
413 user_file_update_needed = true;
414 group_file_update_needed = true;
416 return PointerGetDatum(NULL);
421 * This routine is called during transaction commit or abort.
423 * On commit, if we've written pg_shadow or pg_group during the current
424 * transaction, update the flat files and signal the postmaster.
426 * On abort, just reset the static flags so we don't try to do it on the
427 * next successful commit.
429 * NB: this should be the last step before actual transaction commit.
430 * If any error aborts the transaction after we run this code, the postmaster
431 * will still have received and cached the changed data; so minimize the
432 * window for such problems.
435 AtEOXact_UpdatePasswordFile(bool isCommit)
437 Relation urel = NULL;
438 Relation grel = NULL;
440 if (!(user_file_update_needed || group_file_update_needed))
445 user_file_update_needed = false;
446 group_file_update_needed = false;
451 * We use ExclusiveLock to ensure that only one backend writes the
452 * flat file(s) at a time. That's sufficient because it's okay to
453 * allow plain reads of the tables in parallel. There is some chance
454 * of a deadlock here (if we were triggered by a user update of
455 * pg_shadow or pg_group, which likely won't have gotten a strong
456 * enough lock), so get the locks we need before writing anything.
458 if (user_file_update_needed)
459 urel = heap_openr(ShadowRelationName, ExclusiveLock);
460 if (group_file_update_needed)
461 grel = heap_openr(GroupRelationName, ExclusiveLock);
463 /* Okay to write the files */
464 if (user_file_update_needed)
466 user_file_update_needed = false;
467 write_user_file(urel);
468 heap_close(urel, NoLock);
469 #if defined(WIN32) || defined(__CYGWIN__)
471 /* Rename active file while not holding an exclusive lock */
472 char *filename = user_getfilename(), *filename_new;
474 filename_new = palloc(strlen(filename) + 1 + strlen(".new"));
475 sprintf(filename_new, "%s.new", filename);
476 rename(filename_new, filename);
483 if (group_file_update_needed)
485 group_file_update_needed = false;
486 write_group_file(grel);
487 heap_close(grel, NoLock);
488 #if defined(WIN32) || defined(__CYGWIN__)
490 /* Rename active file while not holding an exclusive lock */
491 char *filename = group_getfilename(), *filename_new;
493 filename_new = palloc(strlen(filename) + 1 + strlen(".new"));
494 sprintf(filename_new, "%s.new", filename);
495 rename(filename_new, filename);
503 * Signal the postmaster to reload its password & group-file cache.
505 SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
514 CreateUser(CreateUserStmt *stmt)
516 Relation pg_shadow_rel;
517 TupleDesc pg_shadow_dsc;
520 Datum new_record[Natts_pg_shadow];
521 char new_record_nulls[Natts_pg_shadow];
522 bool user_exists = false,
523 sysid_exists = false,
528 char *password = NULL; /* PostgreSQL user password */
529 bool encrypt_password = Password_encryption; /* encrypt password? */
530 char encrypted_password[MD5_PASSWD_LEN + 1];
531 int sysid = 0; /* PgSQL system id (valid if havesysid) */
532 bool createdb = false; /* Can the user create databases? */
533 bool createuser = false; /* Can this user create users? */
534 List *groupElts = NIL; /* The groups the user is a member of */
535 char *validUntil = NULL; /* The time the login is valid
537 DefElem *dpassword = NULL;
538 DefElem *dsysid = NULL;
539 DefElem *dcreatedb = NULL;
540 DefElem *dcreateuser = NULL;
541 DefElem *dgroupElts = NULL;
542 DefElem *dvalidUntil = NULL;
544 /* Extract options from the statement node tree */
545 foreach(option, stmt->options)
547 DefElem *defel = (DefElem *) lfirst(option);
549 if (strcmp(defel->defname, "password") == 0 ||
550 strcmp(defel->defname, "encryptedPassword") == 0 ||
551 strcmp(defel->defname, "unencryptedPassword") == 0)
555 (errcode(ERRCODE_SYNTAX_ERROR),
556 errmsg("conflicting or redundant options")));
558 if (strcmp(defel->defname, "encryptedPassword") == 0)
559 encrypt_password = true;
560 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
561 encrypt_password = false;
563 else if (strcmp(defel->defname, "sysid") == 0)
567 (errcode(ERRCODE_SYNTAX_ERROR),
568 errmsg("conflicting or redundant options")));
571 else if (strcmp(defel->defname, "createdb") == 0)
575 (errcode(ERRCODE_SYNTAX_ERROR),
576 errmsg("conflicting or redundant options")));
579 else if (strcmp(defel->defname, "createuser") == 0)
583 (errcode(ERRCODE_SYNTAX_ERROR),
584 errmsg("conflicting or redundant options")));
587 else if (strcmp(defel->defname, "groupElts") == 0)
591 (errcode(ERRCODE_SYNTAX_ERROR),
592 errmsg("conflicting or redundant options")));
595 else if (strcmp(defel->defname, "validUntil") == 0)
599 (errcode(ERRCODE_SYNTAX_ERROR),
600 errmsg("conflicting or redundant options")));
604 elog(ERROR, "option \"%s\" not recognized",
609 createdb = intVal(dcreatedb->arg) != 0;
611 createuser = intVal(dcreateuser->arg) != 0;
614 sysid = intVal(dsysid->arg);
617 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
618 errmsg("user ID must be positive")));
622 validUntil = strVal(dvalidUntil->arg);
624 password = strVal(dpassword->arg);
626 groupElts = (List *) dgroupElts->arg;
628 /* Check some permissions first */
630 CheckPgUserAclNotNull();
634 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
635 errmsg("must be superuser to create users")));
637 if (strcmp(stmt->user, "public") == 0)
639 (errcode(ERRCODE_RESERVED_NAME),
640 errmsg("user name \"%s\" is reserved",
644 * Scan the pg_shadow relation to be certain the user or id doesn't
645 * already exist. Note we secure exclusive lock, because we also need
646 * to be sure of what the next usesysid should be, and we need to
647 * protect our eventual update of the flat password file.
649 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
650 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
652 scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL);
653 max_id = 99; /* start auto-assigned ids at 100 */
654 while (!user_exists && !sysid_exists &&
655 (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
657 Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
660 user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
662 this_sysid = shadow_form->usesysid;
663 if (havesysid) /* customized id wanted */
664 sysid_exists = (this_sysid == sysid);
668 if (this_sysid > max_id)
676 (errcode(ERRCODE_DUPLICATE_OBJECT),
677 errmsg("user \"%s\" already exists",
681 (errcode(ERRCODE_DUPLICATE_OBJECT),
682 errmsg("user ID %d is already assigned", sysid)));
684 /* If no sysid given, use max existing id + 1 */
689 * Build a tuple to insert
691 MemSet(new_record, 0, sizeof(new_record));
692 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
694 new_record[Anum_pg_shadow_usename - 1] =
695 DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
696 new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
697 AssertState(BoolIsValid(createdb));
698 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
699 AssertState(BoolIsValid(createuser));
700 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
701 /* superuser gets catupd right by default */
702 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
706 if (!encrypt_password || isMD5(password))
707 new_record[Anum_pg_shadow_passwd - 1] =
708 DirectFunctionCall1(textin, CStringGetDatum(password));
711 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
713 elog(ERROR, "password encryption failed");
714 new_record[Anum_pg_shadow_passwd - 1] =
715 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
719 new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
722 new_record[Anum_pg_shadow_valuntil - 1] =
723 DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
725 new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
727 new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
729 tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
732 * Insert new record in the pg_shadow table
734 simple_heap_insert(pg_shadow_rel, tuple);
737 CatalogUpdateIndexes(pg_shadow_rel, tuple);
740 * Add the user to the groups specified. We'll just call the below
741 * AlterGroup for this.
743 foreach(item, groupElts)
747 ags.name = strVal(lfirst(item)); /* the group name to add
750 ags.listUsers = makeList1(makeInteger(sysid));
751 AlterGroup(&ags, "CREATE USER");
755 * Now we can clean up; but keep lock until commit (to avoid possible
756 * deadlock when commit code tries to acquire lock).
758 heap_close(pg_shadow_rel, NoLock);
761 * Set flag to update flat password file at commit.
763 user_file_update_needed = true;
772 AlterUser(AlterUserStmt *stmt)
774 Datum new_record[Natts_pg_shadow];
775 char new_record_nulls[Natts_pg_shadow];
776 char new_record_repl[Natts_pg_shadow];
777 Relation pg_shadow_rel;
778 TupleDesc pg_shadow_dsc;
782 char *password = NULL; /* PostgreSQL user password */
783 bool encrypt_password = Password_encryption; /* encrypt password? */
784 char encrypted_password[MD5_PASSWD_LEN + 1];
785 int createdb = -1; /* Can the user create databases? */
786 int createuser = -1; /* Can this user create users? */
787 char *validUntil = NULL; /* The time the login is valid
789 DefElem *dpassword = NULL;
790 DefElem *dcreatedb = NULL;
791 DefElem *dcreateuser = NULL;
792 DefElem *dvalidUntil = NULL;
794 /* Extract options from the statement node tree */
795 foreach(option, stmt->options)
797 DefElem *defel = (DefElem *) lfirst(option);
799 if (strcmp(defel->defname, "password") == 0 ||
800 strcmp(defel->defname, "encryptedPassword") == 0 ||
801 strcmp(defel->defname, "unencryptedPassword") == 0)
805 (errcode(ERRCODE_SYNTAX_ERROR),
806 errmsg("conflicting or redundant options")));
808 if (strcmp(defel->defname, "encryptedPassword") == 0)
809 encrypt_password = true;
810 else if (strcmp(defel->defname, "unencryptedPassword") == 0)
811 encrypt_password = false;
813 else if (strcmp(defel->defname, "createdb") == 0)
817 (errcode(ERRCODE_SYNTAX_ERROR),
818 errmsg("conflicting or redundant options")));
821 else if (strcmp(defel->defname, "createuser") == 0)
825 (errcode(ERRCODE_SYNTAX_ERROR),
826 errmsg("conflicting or redundant options")));
829 else if (strcmp(defel->defname, "validUntil") == 0)
833 (errcode(ERRCODE_SYNTAX_ERROR),
834 errmsg("conflicting or redundant options")));
838 elog(ERROR, "option \"%s\" not recognized",
843 createdb = intVal(dcreatedb->arg);
845 createuser = intVal(dcreateuser->arg);
847 validUntil = strVal(dvalidUntil->arg);
849 password = strVal(dpassword->arg);
852 CheckPgUserAclNotNull();
854 /* must be superuser or just want to change your own password */
860 strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0))
862 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
863 errmsg("permission denied")));
866 * Scan the pg_shadow relation to be certain the user exists. Note we
867 * secure exclusive lock to protect our update of the flat password
870 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
871 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
873 tuple = SearchSysCache(SHADOWNAME,
874 PointerGetDatum(stmt->user),
876 if (!HeapTupleIsValid(tuple))
878 (errcode(ERRCODE_UNDEFINED_OBJECT),
879 errmsg("user \"%s\" does not exist", stmt->user)));
882 * Build an updated tuple, perusing the information just obtained
884 MemSet(new_record, 0, sizeof(new_record));
885 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
886 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
888 new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
889 CStringGetDatum(stmt->user));
890 new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
895 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
896 new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
900 * createuser (superuser) and catupd
902 * XXX It's rather unclear how to handle catupd. It's probably best to
903 * keep it equal to the superuser status, otherwise you could end up
904 * with a situation where no existing superuser can alter the
905 * catalogs, including pg_shadow!
909 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
910 new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
912 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
913 new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
919 if (!encrypt_password || isMD5(password))
920 new_record[Anum_pg_shadow_passwd - 1] =
921 DirectFunctionCall1(textin, CStringGetDatum(password));
924 if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
926 elog(ERROR, "password encryption failed");
927 new_record[Anum_pg_shadow_passwd - 1] =
928 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
930 new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
936 new_record[Anum_pg_shadow_valuntil - 1] =
937 DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
938 new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
941 new_tuple = heap_modifytuple(tuple, pg_shadow_rel, new_record,
942 new_record_nulls, new_record_repl);
943 simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
946 CatalogUpdateIndexes(pg_shadow_rel, new_tuple);
948 ReleaseSysCache(tuple);
949 heap_freetuple(new_tuple);
952 * Now we can clean up; but keep lock until commit (to avoid possible
953 * deadlock when commit code tries to acquire lock).
955 heap_close(pg_shadow_rel, NoLock);
958 * Set flag to update flat password file at commit.
960 user_file_update_needed = true;
968 AlterUserSet(AlterUserSetStmt *stmt)
974 Datum repl_val[Natts_pg_shadow];
975 char repl_null[Natts_pg_shadow];
976 char repl_repl[Natts_pg_shadow];
979 valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
982 * RowExclusiveLock is sufficient, because we don't need to update the
983 * flat password file.
985 rel = heap_openr(ShadowRelationName, RowExclusiveLock);
986 oldtuple = SearchSysCache(SHADOWNAME,
987 PointerGetDatum(stmt->user),
989 if (!HeapTupleIsValid(oldtuple))
991 (errcode(ERRCODE_UNDEFINED_OBJECT),
992 errmsg("user \"%s\" does not exist", stmt->user)));
995 || ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
997 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
998 errmsg("permission denied")));
1000 for (i = 0; i < Natts_pg_shadow; i++)
1003 repl_repl[Anum_pg_shadow_useconfig - 1] = 'r';
1004 if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
1007 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
1015 repl_null[Anum_pg_shadow_useconfig - 1] = ' ';
1017 datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
1018 Anum_pg_shadow_useconfig, &isnull);
1020 array = isnull ? NULL : DatumGetArrayTypeP(datum);
1023 array = GUCArrayAdd(array, stmt->variable, valuestr);
1025 array = GUCArrayDelete(array, stmt->variable);
1028 repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array);
1030 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
1033 newtuple = heap_modifytuple(oldtuple, rel, repl_val, repl_null, repl_repl);
1034 simple_heap_update(rel, &oldtuple->t_self, newtuple);
1036 CatalogUpdateIndexes(rel, newtuple);
1038 ReleaseSysCache(oldtuple);
1039 heap_close(rel, RowExclusiveLock);
1048 DropUser(DropUserStmt *stmt)
1050 Relation pg_shadow_rel;
1051 TupleDesc pg_shadow_dsc;
1056 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1057 errmsg("must be superuser to drop users")));
1060 * Scan the pg_shadow relation to find the usesysid of the user to be
1061 * deleted. Note we secure exclusive lock, because we need to protect
1062 * our update of the flat password file.
1064 pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
1065 pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
1067 foreach(item, stmt->users)
1069 const char *user = strVal(lfirst(item));
1074 ScanKeyData scankey;
1078 tuple = SearchSysCache(SHADOWNAME,
1079 PointerGetDatum(user),
1081 if (!HeapTupleIsValid(tuple))
1083 (errcode(ERRCODE_UNDEFINED_OBJECT),
1084 errmsg("user \"%s\" does not exist", user)));
1086 usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
1088 if (usesysid == GetUserId())
1090 (errcode(ERRCODE_OBJECT_IN_USE),
1091 errmsg("current user cannot be dropped")));
1092 if (usesysid == GetSessionUserId())
1094 (errcode(ERRCODE_OBJECT_IN_USE),
1095 errmsg("session user cannot be dropped")));
1098 * Check if user still owns a database. If so, error out.
1100 * (It used to be that this function would drop the database
1101 * automatically. This is not only very dangerous for people that
1102 * don't read the manual, it doesn't seem to be the behaviour one
1103 * would expect either.) -- petere 2000/01/14)
1105 pg_rel = heap_openr(DatabaseRelationName, AccessShareLock);
1106 pg_dsc = RelationGetDescr(pg_rel);
1108 ScanKeyInit(&scankey,
1109 Anum_pg_database_datdba,
1110 BTEqualStrategyNumber, F_INT4EQ,
1111 Int32GetDatum(usesysid));
1113 scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
1115 if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
1119 dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
1121 (errcode(ERRCODE_OBJECT_IN_USE),
1122 errmsg("user \"%s\" cannot be dropped", user),
1123 errdetail("The user owns database \"%s\".", dbname)));
1127 heap_close(pg_rel, AccessShareLock);
1130 * Somehow we'd have to check for tables, views, etc. owned by the
1131 * user as well, but those could be spread out over all sorts of
1132 * databases which we don't have access to (easily).
1136 * Remove the user from the pg_shadow table
1138 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
1140 ReleaseSysCache(tuple);
1143 * Remove user from groups
1145 * try calling alter group drop user for every group
1147 pg_rel = heap_openr(GroupRelationName, ExclusiveLock);
1148 pg_dsc = RelationGetDescr(pg_rel);
1149 scan = heap_beginscan(pg_rel, SnapshotNow, 0, NULL);
1150 while ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
1154 /* the group name from which to try to drop the user: */
1155 ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
1157 ags.listUsers = makeList1(makeInteger(usesysid));
1158 AlterGroup(&ags, "DROP USER");
1161 heap_close(pg_rel, ExclusiveLock);
1164 * Advance command counter so that later iterations of this loop
1165 * will see the changes already made. This is essential if, for
1166 * example, we are trying to drop two users who are members of the
1167 * same group --- the AlterGroup for the second user had better
1168 * see the tuple updated from the first one.
1170 CommandCounterIncrement();
1174 * Now we can clean up; but keep lock until commit (to avoid possible
1175 * deadlock when commit code tries to acquire lock).
1177 heap_close(pg_shadow_rel, NoLock);
1180 * Set flag to update flat password file at commit.
1182 user_file_update_needed = true;
1190 RenameUser(const char *oldname, const char *newname)
1195 /* ExclusiveLock because we need to update the password file */
1196 rel = heap_openr(ShadowRelationName, ExclusiveLock);
1198 tup = SearchSysCacheCopy(SHADOWNAME,
1199 CStringGetDatum(oldname),
1201 if (!HeapTupleIsValid(tup))
1203 (errcode(ERRCODE_UNDEFINED_OBJECT),
1204 errmsg("user \"%s\" does not exist", oldname)));
1207 * XXX Client applications probably store the session user somewhere,
1208 * so renaming it could cause confusion. On the other hand, there may
1209 * not be an actual problem besides a little confusion, so think about
1212 if (((Form_pg_shadow) GETSTRUCT(tup))->usesysid == GetSessionUserId())
1214 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1215 errmsg("session user may not be renamed")));
1217 /* make sure the new name doesn't exist */
1218 if (SearchSysCacheExists(SHADOWNAME,
1219 CStringGetDatum(newname),
1222 (errcode(ERRCODE_DUPLICATE_OBJECT),
1223 errmsg("user \"%s\" already exists", newname)));
1225 /* must be superuser */
1228 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1229 errmsg("must be superuser to rename users")));
1232 namestrcpy(&(((Form_pg_shadow) GETSTRUCT(tup))->usename), newname);
1233 simple_heap_update(rel, &tup->t_self, tup);
1234 CatalogUpdateIndexes(rel, tup);
1236 heap_close(rel, NoLock);
1237 heap_freetuple(tup);
1239 user_file_update_needed = true;
1244 * CheckPgUserAclNotNull
1246 * check to see if there is an ACL on pg_shadow
1249 CheckPgUserAclNotNull(void)
1253 htup = SearchSysCache(RELOID,
1254 ObjectIdGetDatum(RelOid_pg_shadow),
1256 if (!HeapTupleIsValid(htup)) /* should not happen, we hope */
1257 elog(ERROR, "cache lookup failed for relation %u", RelOid_pg_shadow);
1259 if (heap_attisnull(htup, Anum_pg_class_relacl))
1261 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1262 errmsg("before using passwords you must revoke privileges on %s",
1263 ShadowRelationName),
1264 errdetail("This restriction is to prevent unprivileged users from reading the passwords."),
1265 errhint("Try REVOKE ALL ON \"%s\" FROM PUBLIC.",
1266 ShadowRelationName)));
1268 ReleaseSysCache(htup);
1277 CreateGroup(CreateGroupStmt *stmt)
1279 Relation pg_group_rel;
1282 TupleDesc pg_group_dsc;
1283 bool group_exists = false,
1284 sysid_exists = false,
1287 Datum new_record[Natts_pg_group];
1288 char new_record_nulls[Natts_pg_group];
1294 List *userElts = NIL;
1295 DefElem *dsysid = NULL;
1296 DefElem *duserElts = NULL;
1298 foreach(option, stmt->options)
1300 DefElem *defel = (DefElem *) lfirst(option);
1302 if (strcmp(defel->defname, "sysid") == 0)
1306 (errcode(ERRCODE_SYNTAX_ERROR),
1307 errmsg("conflicting or redundant options")));
1310 else if (strcmp(defel->defname, "userElts") == 0)
1314 (errcode(ERRCODE_SYNTAX_ERROR),
1315 errmsg("conflicting or redundant options")));
1319 elog(ERROR, "option \"%s\" not recognized",
1325 sysid = intVal(dsysid->arg);
1328 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1329 errmsg("group ID must be positive")));
1334 userElts = (List *) duserElts->arg;
1337 * Make sure the user can do this.
1341 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1342 errmsg("must be superuser to create groups")));
1344 if (strcmp(stmt->name, "public") == 0)
1346 (errcode(ERRCODE_RESERVED_NAME),
1347 errmsg("group name \"%s\" is reserved",
1351 * Scan the pg_group relation to be certain the group or id doesn't
1352 * already exist. Note we secure exclusive lock, because we also need
1353 * to be sure of what the next grosysid should be, and we need to
1354 * protect our eventual update of the flat group file.
1356 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1357 pg_group_dsc = RelationGetDescr(pg_group_rel);
1359 scan = heap_beginscan(pg_group_rel, SnapshotNow, 0, NULL);
1360 max_id = 99; /* start auto-assigned ids at 100 */
1361 while (!group_exists && !sysid_exists &&
1362 (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
1364 Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
1367 group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
1369 this_sysid = group_form->grosysid;
1370 if (havesysid) /* customized id wanted */
1371 sysid_exists = (this_sysid == sysid);
1375 if (this_sysid > max_id)
1376 max_id = this_sysid;
1383 (errcode(ERRCODE_DUPLICATE_OBJECT),
1384 errmsg("group \"%s\" already exists",
1388 (errcode(ERRCODE_DUPLICATE_OBJECT),
1389 errmsg("group ID %d is already assigned", sysid)));
1391 /* If no sysid given, use max existing id + 1 */
1396 * Translate the given user names to ids
1398 foreach(item, userElts)
1400 const char *groupuser = strVal(lfirst(item));
1401 int32 userid = get_usesysid(groupuser);
1403 if (!intMember(userid, newlist))
1404 newlist = lappendi(newlist, userid);
1407 /* build an array to insert */
1409 grolist = IdListToArray(newlist);
1414 * Form a tuple to insert
1416 new_record[Anum_pg_group_groname - 1] =
1417 DirectFunctionCall1(namein, CStringGetDatum(stmt->name));
1418 new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid);
1419 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist);
1421 new_record_nulls[Anum_pg_group_groname - 1] = ' ';
1422 new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
1423 new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
1425 tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
1428 * Insert a new record in the pg_group table
1430 simple_heap_insert(pg_group_rel, tuple);
1432 /* Update indexes */
1433 CatalogUpdateIndexes(pg_group_rel, tuple);
1436 * Now we can clean up; but keep lock until commit (to avoid possible
1437 * deadlock when commit code tries to acquire lock).
1439 heap_close(pg_group_rel, NoLock);
1442 * Set flag to update flat group file at commit.
1444 group_file_update_needed = true;
1452 AlterGroup(AlterGroupStmt *stmt, const char *tag)
1454 Relation pg_group_rel;
1455 TupleDesc pg_group_dsc;
1456 HeapTuple group_tuple;
1464 * Make sure the user can do this.
1468 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1469 errmsg("must be superuser to alter groups")));
1472 * Secure exclusive lock to protect our update of the flat group file.
1474 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1475 pg_group_dsc = RelationGetDescr(pg_group_rel);
1478 * Fetch existing tuple for group.
1480 group_tuple = SearchSysCache(GRONAME,
1481 PointerGetDatum(stmt->name),
1483 if (!HeapTupleIsValid(group_tuple))
1485 (errcode(ERRCODE_UNDEFINED_OBJECT),
1486 errmsg("group \"%s\" does not exist", stmt->name)));
1488 /* Fetch old group membership. */
1489 datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
1490 pg_group_dsc, &null);
1491 oldarray = null ? NULL : DatumGetIdListP(datum);
1493 /* initialize list with old array contents */
1494 newlist = IdArrayToList(oldarray);
1497 * Now decide what to do.
1499 AssertState(stmt->action == +1 || stmt->action == -1);
1501 if (stmt->action == +1) /* add users, might also be invoked by
1505 * convert the to be added usernames to sysids and add them to the
1508 foreach(item, stmt->listUsers)
1512 if (strcmp(tag, "ALTER GROUP") == 0)
1514 /* Get the uid of the proposed user to add. */
1515 sysid = get_usesysid(strVal(lfirst(item)));
1517 else if (strcmp(tag, "CREATE USER") == 0)
1520 * in this case we already know the uid and it wouldn't be
1521 * in the cache anyway yet
1523 sysid = intVal(lfirst(item));
1527 elog(ERROR, "unexpected tag: \"%s\"", tag);
1528 sysid = 0; /* keep compiler quiet */
1531 if (!intMember(sysid, newlist))
1532 newlist = lappendi(newlist, sysid);
1536 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1537 } /* endif alter group add user */
1539 else if (stmt->action == -1) /* drop users from group */
1541 bool is_dropuser = strcmp(tag, "DROP USER") == 0;
1547 (errcode(ERRCODE_WARNING),
1548 errmsg("group \"%s\" does not have any members",
1554 * convert the to be dropped usernames to sysids and remove
1555 * them from the list
1557 foreach(item, stmt->listUsers)
1563 /* Get the uid of the proposed user to drop. */
1564 sysid = get_usesysid(strVal(lfirst(item)));
1568 /* for dropuser we already know the uid */
1569 sysid = intVal(lfirst(item));
1571 if (intMember(sysid, newlist))
1572 newlist = lremovei(sysid, newlist);
1573 else if (!is_dropuser)
1575 (errcode(ERRCODE_WARNING),
1576 errmsg("user \"%s\" is not in group \"%s\"",
1577 strVal(lfirst(item)), stmt->name)));
1581 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1582 } /* endif group not null */
1583 } /* endif alter group drop user */
1585 ReleaseSysCache(group_tuple);
1588 * Now we can clean up; but keep lock until commit (to avoid possible
1589 * deadlock when commit code tries to acquire lock).
1591 heap_close(pg_group_rel, NoLock);
1594 * Set flag to update flat group file at commit.
1596 group_file_update_needed = true;
1600 * Subroutine for AlterGroup: given a pg_group tuple and a desired new
1601 * membership (expressed as an integer list), form and write an updated tuple.
1602 * The pg_group relation must be open and locked already.
1605 UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
1609 Datum new_record[Natts_pg_group];
1610 char new_record_nulls[Natts_pg_group];
1611 char new_record_repl[Natts_pg_group];
1614 newarray = IdListToArray(members);
1617 * Form an updated tuple with the new array and write it back.
1619 MemSet(new_record, 0, sizeof(new_record));
1620 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1621 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1623 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1624 new_record_repl[Anum_pg_group_grolist - 1] = 'r';
1626 tuple = heap_modifytuple(group_tuple, group_rel,
1627 new_record, new_record_nulls, new_record_repl);
1629 simple_heap_update(group_rel, &group_tuple->t_self, tuple);
1631 /* Update indexes */
1632 CatalogUpdateIndexes(group_rel, tuple);
1637 * Convert an integer list of sysids to an array.
1640 IdListToArray(List *members)
1642 int nmembers = length(members);
1647 newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
1648 newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
1649 newarray->flags = 0;
1650 newarray->elemtype = INT4OID;
1651 ARR_NDIM(newarray) = 1; /* one dimensional array */
1652 ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */
1653 ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */
1655 foreach(item, members)
1656 ((int *) ARR_DATA_PTR(newarray))[i++] = lfirsti(item);
1662 * Convert an array of sysids to an integer list.
1665 IdArrayToList(IdList *oldarray)
1667 List *newlist = NIL;
1671 if (oldarray == NULL)
1674 Assert(ARR_NDIM(oldarray) == 1);
1675 Assert(ARR_ELEMTYPE(oldarray) == INT4OID);
1677 hibound = ARR_DIMS(oldarray)[0];
1679 for (i = 0; i < hibound; i++)
1683 sysid = ((int32 *) ARR_DATA_PTR(oldarray))[i];
1684 /* filter out any duplicates --- probably a waste of time */
1685 if (!intMember(sysid, newlist))
1686 newlist = lappendi(newlist, sysid);
1697 DropGroup(DropGroupStmt *stmt)
1699 Relation pg_group_rel;
1703 * Make sure the user can do this.
1707 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1708 errmsg("must be superuser to drop groups")));
1711 * Secure exclusive lock to protect our update of the flat group file.
1713 pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1715 /* Find and delete the group. */
1717 tuple = SearchSysCacheCopy(GRONAME,
1718 PointerGetDatum(stmt->name),
1720 if (!HeapTupleIsValid(tuple))
1722 (errcode(ERRCODE_UNDEFINED_OBJECT),
1723 errmsg("group \"%s\" does not exist", stmt->name)));
1725 simple_heap_delete(pg_group_rel, &tuple->t_self);
1728 * Now we can clean up; but keep lock until commit (to avoid possible
1729 * deadlock when commit code tries to acquire lock).
1731 heap_close(pg_group_rel, NoLock);
1734 * Set flag to update flat group file at commit.
1736 group_file_update_needed = true;
1744 RenameGroup(const char *oldname, const char *newname)
1749 /* ExclusiveLock because we need to update the flat group file */
1750 rel = heap_openr(GroupRelationName, ExclusiveLock);
1752 tup = SearchSysCacheCopy(GRONAME,
1753 CStringGetDatum(oldname),
1755 if (!HeapTupleIsValid(tup))
1757 (errcode(ERRCODE_UNDEFINED_OBJECT),
1758 errmsg("group \"%s\" does not exist", oldname)));
1760 /* make sure the new name doesn't exist */
1761 if (SearchSysCacheExists(GRONAME,
1762 CStringGetDatum(newname),
1765 (errcode(ERRCODE_DUPLICATE_OBJECT),
1766 errmsg("group \"%s\" already exists", newname)));
1768 /* must be superuser */
1771 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1772 errmsg("must be superuser to rename groups")));
1775 namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
1776 simple_heap_update(rel, &tup->t_self, tup);
1777 CatalogUpdateIndexes(rel, tup);
1779 heap_close(rel, NoLock);
1780 heap_freetuple(tup);
1782 group_file_update_needed = true;