]> granicus.if.org Git - postgresql/blob - src/backend/commands/user.c
For application to HEAD, following community review.
[postgresql] / src / backend / commands / user.c
1 /*-------------------------------------------------------------------------
2  *
3  * user.c
4  *        Commands for manipulating users and groups.
5  *
6  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.138 2004/02/25 19:41:22 momjian Exp $
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
14
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <unistd.h>
19
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"
39
40
41 #define PWD_FILE                "pg_pwd"
42 #define USER_GROUP_FILE "pg_group"
43
44
45 extern bool Password_encryption;
46
47 static bool user_file_update_needed = false;
48 static bool group_file_update_needed = false;
49
50
51 static void CheckPgUserAclNotNull(void);
52 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
53                                           List *members);
54 static IdList *IdListToArray(List *members);
55 static List *IdArrayToList(IdList *oldarray);
56
57
58 /*
59  *      fputs_quote
60  *
61  *      Outputs string in quotes, with double-quotes duplicated.
62  *      We could use quote_ident(), but that expects a TEXT argument.
63  */
64 static void
65 fputs_quote(char *str, FILE *fp)
66 {
67         fputc('"', fp);
68         while (*str)
69         {
70                 fputc(*str, fp);
71                 if (*str == '"')
72                         fputc('"', fp);
73                 str++;
74         }
75         fputc('"', fp);
76 }
77
78
79 /*
80  * group_getfilename --- get full pathname of group file
81  *
82  * Note that result string is palloc'd, and should be freed by the caller.
83  */
84 char *
85 group_getfilename(void)
86 {
87         int                     bufsize;
88         char       *pfnam;
89
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);
94
95         return pfnam;
96 }
97
98
99 /*
100  * Get full pathname of password file.
101  *
102  * Note that result string is palloc'd, and should be freed by the caller.
103  */
104 char *
105 user_getfilename(void)
106 {
107         int                     bufsize;
108         char       *pfnam;
109
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);
114
115         return pfnam;
116 }
117
118
119 /*
120  * write_group_file: update the flat group file
121  */
122 static void
123 write_group_file(Relation grel)
124 {
125         char       *filename,
126                            *tempname;
127         int                     bufsize;
128         FILE       *fp;
129         mode_t          oumask;
130         HeapScanDesc scan;
131         HeapTuple       tuple;
132         TupleDesc       dsc = RelationGetDescr(grel);
133
134         /*
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.
138          */
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");
146 #endif
147
148         oumask = umask((mode_t) 077);
149         fp = AllocateFile(tempname, "w");
150         umask(oumask);
151         if (fp == NULL)
152                 ereport(ERROR,
153                                 (errcode_for_file_access(),
154                           errmsg("could not write to temporary file \"%s\": %m", tempname)));
155
156         /*
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?)
160          */
161         scan = heap_beginscan(grel, SnapshotSelf, 0, NULL);
162         while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
163         {
164                 Datum           datum,
165                                         grolist_datum;
166                 bool            isnull;
167                 char       *groname;
168                 IdList     *grolist_p;
169                 AclId      *aidp;
170                 int                     i,
171                                         j,
172                                         num;
173                 char       *usename;
174                 bool            first_user = true;
175
176                 datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull);
177                 /* ignore NULL groupnames --- shouldn't happen */
178                 if (isnull)
179                         continue;
180                 groname = NameStr(*DatumGetName(datum));
181
182                 /*
183                  * Check for invalid characters in the group name.
184                  */
185                 i = strcspn(groname, "\n");
186                 if (groname[i] != '\0')
187                 {
188                         ereport(LOG,
189                                         (errmsg("invalid group name \"%s\"", groname)));
190                         continue;
191                 }
192
193                 grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull);
194                 /* Ignore NULL group lists */
195                 if (isnull)
196                         continue;
197
198                 /* be sure the IdList is not toasted */
199                 grolist_p = DatumGetIdListP(grolist_datum);
200
201                 /* scan grolist */
202                 num = IDLIST_NUM(grolist_p);
203                 aidp = IDLIST_DAT(grolist_p);
204                 for (i = 0; i < num; ++i)
205                 {
206                         tuple = SearchSysCache(SHADOWSYSID,
207                                                                    PointerGetDatum(aidp[i]),
208                                                                    0, 0, 0);
209                         if (HeapTupleIsValid(tuple))
210                         {
211                                 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
212
213                                 /*
214                                  * Check for illegal characters in the user name.
215                                  */
216                                 j = strcspn(usename, "\n");
217                                 if (usename[j] != '\0')
218                                 {
219                                         ereport(LOG,
220                                                   (errmsg("invalid user name \"%s\"", usename)));
221                                         continue;
222                                 }
223
224                                 /*
225                                  * File format is: "dbname"    "user1" "user2" "user3"
226                                  */
227                                 if (first_user)
228                                 {
229                                         fputs_quote(groname, fp);
230                                         fputs("\t", fp);
231                                 }
232                                 else
233                                         fputs(" ", fp);
234
235                                 first_user = false;
236                                 fputs_quote(usename, fp);
237
238                                 ReleaseSysCache(tuple);
239                         }
240                 }
241                 if (!first_user)
242                         fputs("\n", fp);
243                 /* if IdList was toasted, free detoasted copy */
244                 if ((Pointer) grolist_p != DatumGetPointer(grolist_datum))
245                         pfree(grolist_p);
246         }
247         heap_endscan(scan);
248
249         if (FreeFile(fp))
250                 ereport(ERROR,
251                                 (errcode_for_file_access(),
252                                  errmsg("could not write to temporary file \"%s\": %m",
253                                                 tempname)));
254
255         /*
256          * Rename the temp file to its final name, deleting the old pg_pwd. We
257          * expect that rename(2) is an atomic action.
258          */
259         if (rename(tempname, filename))
260                 ereport(ERROR,
261                                 (errcode_for_file_access(),
262                                  errmsg("could not rename file \"%s\" to \"%s\": %m",
263                                                 tempname, filename)));
264
265         pfree((void *) tempname);
266         pfree((void *) filename);
267 }
268
269
270 /*
271  * write_user_file: update the flat password file
272  */
273 static void
274 write_user_file(Relation urel)
275 {
276         char       *filename,
277                            *tempname;
278         int                     bufsize;
279         FILE       *fp;
280         mode_t          oumask;
281         HeapScanDesc scan;
282         HeapTuple       tuple;
283         TupleDesc       dsc = RelationGetDescr(urel);
284
285         /*
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.
289          */
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");
297 #endif
298
299         oumask = umask((mode_t) 077);
300         fp = AllocateFile(tempname, "w");
301         umask(oumask);
302         if (fp == NULL)
303                 ereport(ERROR,
304                                 (errcode_for_file_access(),
305                           errmsg("could not write to temporary file \"%s\": %m", tempname)));
306
307         /*
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?)
311          */
312         scan = heap_beginscan(urel, SnapshotSelf, 0, NULL);
313         while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
314         {
315                 Datum           datum;
316                 bool            isnull;
317                 char       *usename,
318                                    *passwd,
319                                    *valuntil;
320                 int                     i;
321
322                 datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull);
323                 /* ignore NULL usernames (shouldn't happen) */
324                 if (isnull)
325                         continue;
326                 usename = NameStr(*DatumGetName(datum));
327
328                 datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull);
329
330                 /*
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.
336                  */
337                 if (isnull)
338                         continue;
339
340                 passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
341
342                 datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull);
343                 if (isnull)
344                         valuntil = pstrdup("");
345                 else
346                         valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum));
347
348                 /*
349                  * Check for illegal characters in the username and password.
350                  */
351                 i = strcspn(usename, "\n");
352                 if (usename[i] != '\0')
353                 {
354                         ereport(LOG,
355                                         (errmsg("invalid user name \"%s\"", usename)));
356                         continue;
357                 }
358                 i = strcspn(passwd, "\n");
359                 if (passwd[i] != '\0')
360                 {
361                         ereport(LOG,
362                                         (errmsg("invalid user password \"%s\"", passwd)));
363                         continue;
364                 }
365
366                 /*
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
369                  * be adjusted.
370                  */
371                 fputs_quote(usename, fp);
372                 fputs(" ", fp);
373                 fputs_quote(passwd, fp);
374                 fputs(" ", fp);
375                 fputs_quote(valuntil, fp);
376                 fputs("\n", fp);
377
378                 pfree(passwd);
379                 pfree(valuntil);
380         }
381         heap_endscan(scan);
382
383         if (FreeFile(fp))
384                 ereport(ERROR,
385                                 (errcode_for_file_access(),
386                                  errmsg("could not write to temporary file \"%s\": %m",
387                                                 tempname)));
388
389         /*
390          * Rename the temp file to its final name, deleting the old pg_pwd. We
391          * expect that rename(2) is an atomic action.
392          */
393         if (rename(tempname, filename))
394                 ereport(ERROR,
395                                 (errcode_for_file_access(),
396                                  errmsg("could not rename file \"%s\" to \"%s\": %m",
397                                                 tempname, filename)));
398
399         pfree((void *) tempname);
400         pfree((void *) filename);
401 }
402
403
404 /*
405  * This trigger is fired whenever someone modifies pg_shadow or pg_group
406  * via general-purpose INSERT/UPDATE/DELETE commands.
407  *
408  * XXX should probably have two separate triggers.
409  */
410 Datum
411 update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
412 {
413         user_file_update_needed = true;
414         group_file_update_needed = true;
415
416         return PointerGetDatum(NULL);
417 }
418
419
420 /*
421  * This routine is called during transaction commit or abort.
422  *
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.
425  *
426  * On abort, just reset the static flags so we don't try to do it on the
427  * next successful commit.
428  *
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.
433  */
434 void
435 AtEOXact_UpdatePasswordFile(bool isCommit)
436 {
437         Relation        urel = NULL;
438         Relation        grel = NULL;
439
440         if (!(user_file_update_needed || group_file_update_needed))
441                 return;
442
443         if (!isCommit)
444         {
445                 user_file_update_needed = false;
446                 group_file_update_needed = false;
447                 return;
448         }
449
450         /*
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.
457          */
458         if (user_file_update_needed)
459                 urel = heap_openr(ShadowRelationName, ExclusiveLock);
460         if (group_file_update_needed)
461                 grel = heap_openr(GroupRelationName, ExclusiveLock);
462
463         /* Okay to write the files */
464         if (user_file_update_needed)
465         {
466                 user_file_update_needed = false;
467                 write_user_file(urel);
468                 heap_close(urel, NoLock);
469 #if defined(WIN32) || defined(__CYGWIN__)
470                 {
471                         /* Rename active file while not holding an exclusive lock */
472                         char *filename = user_getfilename(), *filename_new;
473
474                         filename_new = palloc(strlen(filename) + 1 + strlen(".new"));
475                         sprintf(filename_new, "%s.new", filename);
476                         rename(filename_new, filename);
477                         pfree(filename);
478                         pfree(filename_new);
479                 }
480 #endif
481         }
482
483         if (group_file_update_needed)
484         {
485                 group_file_update_needed = false;
486                 write_group_file(grel);
487                 heap_close(grel, NoLock);
488 #if defined(WIN32) || defined(__CYGWIN__)
489                 {
490                         /* Rename active file while not holding an exclusive lock */
491                         char *filename = group_getfilename(), *filename_new;
492
493                         filename_new = palloc(strlen(filename) + 1 + strlen(".new"));
494                         sprintf(filename_new, "%s.new", filename);
495                         rename(filename_new, filename);
496                         pfree(filename);
497                         pfree(filename_new);
498                 }
499 #endif
500         }
501
502         /*
503          * Signal the postmaster to reload its password & group-file cache.
504          */
505         SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
506 }
507
508
509
510 /*
511  * CREATE USER
512  */
513 void
514 CreateUser(CreateUserStmt *stmt)
515 {
516         Relation        pg_shadow_rel;
517         TupleDesc       pg_shadow_dsc;
518         HeapScanDesc scan;
519         HeapTuple       tuple;
520         Datum           new_record[Natts_pg_shadow];
521         char            new_record_nulls[Natts_pg_shadow];
522         bool            user_exists = false,
523                                 sysid_exists = false,
524                                 havesysid = false;
525         int                     max_id;
526         List       *item,
527                            *option;
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
536                                                                                  * until */
537         DefElem    *dpassword = NULL;
538         DefElem    *dsysid = NULL;
539         DefElem    *dcreatedb = NULL;
540         DefElem    *dcreateuser = NULL;
541         DefElem    *dgroupElts = NULL;
542         DefElem    *dvalidUntil = NULL;
543
544         /* Extract options from the statement node tree */
545         foreach(option, stmt->options)
546         {
547                 DefElem    *defel = (DefElem *) lfirst(option);
548
549                 if (strcmp(defel->defname, "password") == 0 ||
550                         strcmp(defel->defname, "encryptedPassword") == 0 ||
551                         strcmp(defel->defname, "unencryptedPassword") == 0)
552                 {
553                         if (dpassword)
554                                 ereport(ERROR,
555                                                 (errcode(ERRCODE_SYNTAX_ERROR),
556                                                  errmsg("conflicting or redundant options")));
557                         dpassword = defel;
558                         if (strcmp(defel->defname, "encryptedPassword") == 0)
559                                 encrypt_password = true;
560                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
561                                 encrypt_password = false;
562                 }
563                 else if (strcmp(defel->defname, "sysid") == 0)
564                 {
565                         if (dsysid)
566                                 ereport(ERROR,
567                                                 (errcode(ERRCODE_SYNTAX_ERROR),
568                                                  errmsg("conflicting or redundant options")));
569                         dsysid = defel;
570                 }
571                 else if (strcmp(defel->defname, "createdb") == 0)
572                 {
573                         if (dcreatedb)
574                                 ereport(ERROR,
575                                                 (errcode(ERRCODE_SYNTAX_ERROR),
576                                                  errmsg("conflicting or redundant options")));
577                         dcreatedb = defel;
578                 }
579                 else if (strcmp(defel->defname, "createuser") == 0)
580                 {
581                         if (dcreateuser)
582                                 ereport(ERROR,
583                                                 (errcode(ERRCODE_SYNTAX_ERROR),
584                                                  errmsg("conflicting or redundant options")));
585                         dcreateuser = defel;
586                 }
587                 else if (strcmp(defel->defname, "groupElts") == 0)
588                 {
589                         if (dgroupElts)
590                                 ereport(ERROR,
591                                                 (errcode(ERRCODE_SYNTAX_ERROR),
592                                                  errmsg("conflicting or redundant options")));
593                         dgroupElts = defel;
594                 }
595                 else if (strcmp(defel->defname, "validUntil") == 0)
596                 {
597                         if (dvalidUntil)
598                                 ereport(ERROR,
599                                                 (errcode(ERRCODE_SYNTAX_ERROR),
600                                                  errmsg("conflicting or redundant options")));
601                         dvalidUntil = defel;
602                 }
603                 else
604                         elog(ERROR, "option \"%s\" not recognized",
605                                  defel->defname);
606         }
607
608         if (dcreatedb)
609                 createdb = intVal(dcreatedb->arg) != 0;
610         if (dcreateuser)
611                 createuser = intVal(dcreateuser->arg) != 0;
612         if (dsysid)
613         {
614                 sysid = intVal(dsysid->arg);
615                 if (sysid <= 0)
616                         ereport(ERROR,
617                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
618                                          errmsg("user ID must be positive")));
619                 havesysid = true;
620         }
621         if (dvalidUntil)
622                 validUntil = strVal(dvalidUntil->arg);
623         if (dpassword)
624                 password = strVal(dpassword->arg);
625         if (dgroupElts)
626                 groupElts = (List *) dgroupElts->arg;
627
628         /* Check some permissions first */
629         if (password)
630                 CheckPgUserAclNotNull();
631
632         if (!superuser())
633                 ereport(ERROR,
634                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
635                                  errmsg("must be superuser to create users")));
636
637         if (strcmp(stmt->user, "public") == 0)
638                 ereport(ERROR,
639                                 (errcode(ERRCODE_RESERVED_NAME),
640                                  errmsg("user name \"%s\" is reserved",
641                                                 stmt->user)));
642
643         /*
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.
648          */
649         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
650         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
651
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)
656         {
657                 Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
658                 int32           this_sysid;
659
660                 user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
661
662                 this_sysid = shadow_form->usesysid;
663                 if (havesysid)                  /* customized id wanted */
664                         sysid_exists = (this_sysid == sysid);
665                 else
666                 {
667                         /* pick 1 + max */
668                         if (this_sysid > max_id)
669                                 max_id = this_sysid;
670                 }
671         }
672         heap_endscan(scan);
673
674         if (user_exists)
675                 ereport(ERROR,
676                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
677                                  errmsg("user \"%s\" already exists",
678                                                 stmt->user)));
679         if (sysid_exists)
680                 ereport(ERROR,
681                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
682                                  errmsg("user ID %d is already assigned", sysid)));
683
684         /* If no sysid given, use max existing id + 1 */
685         if (!havesysid)
686                 sysid = max_id + 1;
687
688         /*
689          * Build a tuple to insert
690          */
691         MemSet(new_record, 0, sizeof(new_record));
692         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
693
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);
703
704         if (password)
705         {
706                 if (!encrypt_password || isMD5(password))
707                         new_record[Anum_pg_shadow_passwd - 1] =
708                                 DirectFunctionCall1(textin, CStringGetDatum(password));
709                 else
710                 {
711                         if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
712                                                         encrypted_password))
713                                 elog(ERROR, "password encryption failed");
714                         new_record[Anum_pg_shadow_passwd - 1] =
715                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
716                 }
717         }
718         else
719                 new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
720
721         if (validUntil)
722                 new_record[Anum_pg_shadow_valuntil - 1] =
723                         DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
724         else
725                 new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
726
727         new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
728
729         tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
730
731         /*
732          * Insert new record in the pg_shadow table
733          */
734         simple_heap_insert(pg_shadow_rel, tuple);
735
736         /* Update indexes */
737         CatalogUpdateIndexes(pg_shadow_rel, tuple);
738
739         /*
740          * Add the user to the groups specified. We'll just call the below
741          * AlterGroup for this.
742          */
743         foreach(item, groupElts)
744         {
745                 AlterGroupStmt ags;
746
747                 ags.name = strVal(lfirst(item));                /* the group name to add
748                                                                                                  * this in */
749                 ags.action = +1;
750                 ags.listUsers = makeList1(makeInteger(sysid));
751                 AlterGroup(&ags, "CREATE USER");
752         }
753
754         /*
755          * Now we can clean up; but keep lock until commit (to avoid possible
756          * deadlock when commit code tries to acquire lock).
757          */
758         heap_close(pg_shadow_rel, NoLock);
759
760         /*
761          * Set flag to update flat password file at commit.
762          */
763         user_file_update_needed = true;
764 }
765
766
767
768 /*
769  * ALTER USER
770  */
771 void
772 AlterUser(AlterUserStmt *stmt)
773 {
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;
779         HeapTuple       tuple,
780                                 new_tuple;
781         List       *option;
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
788                                                                                  * until */
789         DefElem    *dpassword = NULL;
790         DefElem    *dcreatedb = NULL;
791         DefElem    *dcreateuser = NULL;
792         DefElem    *dvalidUntil = NULL;
793
794         /* Extract options from the statement node tree */
795         foreach(option, stmt->options)
796         {
797                 DefElem    *defel = (DefElem *) lfirst(option);
798
799                 if (strcmp(defel->defname, "password") == 0 ||
800                         strcmp(defel->defname, "encryptedPassword") == 0 ||
801                         strcmp(defel->defname, "unencryptedPassword") == 0)
802                 {
803                         if (dpassword)
804                                 ereport(ERROR,
805                                                 (errcode(ERRCODE_SYNTAX_ERROR),
806                                                  errmsg("conflicting or redundant options")));
807                         dpassword = defel;
808                         if (strcmp(defel->defname, "encryptedPassword") == 0)
809                                 encrypt_password = true;
810                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
811                                 encrypt_password = false;
812                 }
813                 else if (strcmp(defel->defname, "createdb") == 0)
814                 {
815                         if (dcreatedb)
816                                 ereport(ERROR,
817                                                 (errcode(ERRCODE_SYNTAX_ERROR),
818                                                  errmsg("conflicting or redundant options")));
819                         dcreatedb = defel;
820                 }
821                 else if (strcmp(defel->defname, "createuser") == 0)
822                 {
823                         if (dcreateuser)
824                                 ereport(ERROR,
825                                                 (errcode(ERRCODE_SYNTAX_ERROR),
826                                                  errmsg("conflicting or redundant options")));
827                         dcreateuser = defel;
828                 }
829                 else if (strcmp(defel->defname, "validUntil") == 0)
830                 {
831                         if (dvalidUntil)
832                                 ereport(ERROR,
833                                                 (errcode(ERRCODE_SYNTAX_ERROR),
834                                                  errmsg("conflicting or redundant options")));
835                         dvalidUntil = defel;
836                 }
837                 else
838                         elog(ERROR, "option \"%s\" not recognized",
839                                  defel->defname);
840         }
841
842         if (dcreatedb)
843                 createdb = intVal(dcreatedb->arg);
844         if (dcreateuser)
845                 createuser = intVal(dcreateuser->arg);
846         if (dvalidUntil)
847                 validUntil = strVal(dvalidUntil->arg);
848         if (dpassword)
849                 password = strVal(dpassword->arg);
850
851         if (password)
852                 CheckPgUserAclNotNull();
853
854         /* must be superuser or just want to change your own password */
855         if (!superuser() &&
856                 !(createdb < 0 &&
857                   createuser < 0 &&
858                   !validUntil &&
859                   password &&
860                   strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0))
861                 ereport(ERROR,
862                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
863                                  errmsg("permission denied")));
864
865         /*
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
868          * file.
869          */
870         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
871         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
872
873         tuple = SearchSysCache(SHADOWNAME,
874                                                    PointerGetDatum(stmt->user),
875                                                    0, 0, 0);
876         if (!HeapTupleIsValid(tuple))
877                 ereport(ERROR,
878                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
879                                  errmsg("user \"%s\" does not exist", stmt->user)));
880
881         /*
882          * Build an updated tuple, perusing the information just obtained
883          */
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));
887
888         new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
889                                                                                         CStringGetDatum(stmt->user));
890         new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
891
892         /* createdb */
893         if (createdb >= 0)
894         {
895                 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
896                 new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
897         }
898
899         /*
900          * createuser (superuser) and catupd
901          *
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!
906          */
907         if (createuser >= 0)
908         {
909                 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
910                 new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
911
912                 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
913                 new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
914         }
915
916         /* password */
917         if (password)
918         {
919                 if (!encrypt_password || isMD5(password))
920                         new_record[Anum_pg_shadow_passwd - 1] =
921                                 DirectFunctionCall1(textin, CStringGetDatum(password));
922                 else
923                 {
924                         if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
925                                                         encrypted_password))
926                                 elog(ERROR, "password encryption failed");
927                         new_record[Anum_pg_shadow_passwd - 1] =
928                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
929                 }
930                 new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
931         }
932
933         /* valid until */
934         if (validUntil)
935         {
936                 new_record[Anum_pg_shadow_valuntil - 1] =
937                         DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
938                 new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
939         }
940
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);
944
945         /* Update indexes */
946         CatalogUpdateIndexes(pg_shadow_rel, new_tuple);
947
948         ReleaseSysCache(tuple);
949         heap_freetuple(new_tuple);
950
951         /*
952          * Now we can clean up; but keep lock until commit (to avoid possible
953          * deadlock when commit code tries to acquire lock).
954          */
955         heap_close(pg_shadow_rel, NoLock);
956
957         /*
958          * Set flag to update flat password file at commit.
959          */
960         user_file_update_needed = true;
961 }
962
963
964 /*
965  * ALTER USER ... SET
966  */
967 void
968 AlterUserSet(AlterUserSetStmt *stmt)
969 {
970         char       *valuestr;
971         HeapTuple       oldtuple,
972                                 newtuple;
973         Relation        rel;
974         Datum           repl_val[Natts_pg_shadow];
975         char            repl_null[Natts_pg_shadow];
976         char            repl_repl[Natts_pg_shadow];
977         int                     i;
978
979         valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
980
981         /*
982          * RowExclusiveLock is sufficient, because we don't need to update the
983          * flat password file.
984          */
985         rel = heap_openr(ShadowRelationName, RowExclusiveLock);
986         oldtuple = SearchSysCache(SHADOWNAME,
987                                                           PointerGetDatum(stmt->user),
988                                                           0, 0, 0);
989         if (!HeapTupleIsValid(oldtuple))
990                 ereport(ERROR,
991                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
992                                  errmsg("user \"%s\" does not exist", stmt->user)));
993
994         if (!(superuser()
995          || ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
996                 ereport(ERROR,
997                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
998                                  errmsg("permission denied")));
999
1000         for (i = 0; i < Natts_pg_shadow; i++)
1001                 repl_repl[i] = ' ';
1002
1003         repl_repl[Anum_pg_shadow_useconfig - 1] = 'r';
1004         if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
1005         {
1006                 /* RESET ALL */
1007                 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
1008         }
1009         else
1010         {
1011                 Datum           datum;
1012                 bool            isnull;
1013                 ArrayType  *array;
1014
1015                 repl_null[Anum_pg_shadow_useconfig - 1] = ' ';
1016
1017                 datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
1018                                                                 Anum_pg_shadow_useconfig, &isnull);
1019
1020                 array = isnull ? NULL : DatumGetArrayTypeP(datum);
1021
1022                 if (valuestr)
1023                         array = GUCArrayAdd(array, stmt->variable, valuestr);
1024                 else
1025                         array = GUCArrayDelete(array, stmt->variable);
1026
1027                 if (array)
1028                         repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array);
1029                 else
1030                         repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
1031         }
1032
1033         newtuple = heap_modifytuple(oldtuple, rel, repl_val, repl_null, repl_repl);
1034         simple_heap_update(rel, &oldtuple->t_self, newtuple);
1035
1036         CatalogUpdateIndexes(rel, newtuple);
1037
1038         ReleaseSysCache(oldtuple);
1039         heap_close(rel, RowExclusiveLock);
1040 }
1041
1042
1043
1044 /*
1045  * DROP USER
1046  */
1047 void
1048 DropUser(DropUserStmt *stmt)
1049 {
1050         Relation        pg_shadow_rel;
1051         TupleDesc       pg_shadow_dsc;
1052         List       *item;
1053
1054         if (!superuser())
1055                 ereport(ERROR,
1056                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1057                                  errmsg("must be superuser to drop users")));
1058
1059         /*
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.
1063          */
1064         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
1065         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
1066
1067         foreach(item, stmt->users)
1068         {
1069                 const char *user = strVal(lfirst(item));
1070                 HeapTuple       tuple,
1071                                         tmp_tuple;
1072                 Relation        pg_rel;
1073                 TupleDesc       pg_dsc;
1074                 ScanKeyData scankey;
1075                 HeapScanDesc scan;
1076                 AclId           usesysid;
1077
1078                 tuple = SearchSysCache(SHADOWNAME,
1079                                                            PointerGetDatum(user),
1080                                                            0, 0, 0);
1081                 if (!HeapTupleIsValid(tuple))
1082                         ereport(ERROR,
1083                                         (errcode(ERRCODE_UNDEFINED_OBJECT),
1084                                          errmsg("user \"%s\" does not exist", user)));
1085
1086                 usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
1087
1088                 if (usesysid == GetUserId())
1089                         ereport(ERROR,
1090                                         (errcode(ERRCODE_OBJECT_IN_USE),
1091                                          errmsg("current user cannot be dropped")));
1092                 if (usesysid == GetSessionUserId())
1093                         ereport(ERROR,
1094                                         (errcode(ERRCODE_OBJECT_IN_USE),
1095                                          errmsg("session user cannot be dropped")));
1096
1097                 /*
1098                  * Check if user still owns a database. If so, error out.
1099                  *
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)
1104                  */
1105                 pg_rel = heap_openr(DatabaseRelationName, AccessShareLock);
1106                 pg_dsc = RelationGetDescr(pg_rel);
1107
1108                 ScanKeyInit(&scankey,
1109                                         Anum_pg_database_datdba,
1110                                         BTEqualStrategyNumber, F_INT4EQ,
1111                                         Int32GetDatum(usesysid));
1112
1113                 scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
1114
1115                 if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
1116                 {
1117                         char       *dbname;
1118
1119                         dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
1120                         ereport(ERROR,
1121                                         (errcode(ERRCODE_OBJECT_IN_USE),
1122                                          errmsg("user \"%s\" cannot be dropped", user),
1123                                    errdetail("The user owns database \"%s\".", dbname)));
1124                 }
1125
1126                 heap_endscan(scan);
1127                 heap_close(pg_rel, AccessShareLock);
1128
1129                 /*
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).
1133                  */
1134
1135                 /*
1136                  * Remove the user from the pg_shadow table
1137                  */
1138                 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
1139
1140                 ReleaseSysCache(tuple);
1141
1142                 /*
1143                  * Remove user from groups
1144                  *
1145                  * try calling alter group drop user for every group
1146                  */
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)
1151                 {
1152                         AlterGroupStmt ags;
1153
1154                         /* the group name from which to try to drop the user: */
1155                         ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
1156                         ags.action = -1;
1157                         ags.listUsers = makeList1(makeInteger(usesysid));
1158                         AlterGroup(&ags, "DROP USER");
1159                 }
1160                 heap_endscan(scan);
1161                 heap_close(pg_rel, ExclusiveLock);
1162
1163                 /*
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.
1169                  */
1170                 CommandCounterIncrement();
1171         }
1172
1173         /*
1174          * Now we can clean up; but keep lock until commit (to avoid possible
1175          * deadlock when commit code tries to acquire lock).
1176          */
1177         heap_close(pg_shadow_rel, NoLock);
1178
1179         /*
1180          * Set flag to update flat password file at commit.
1181          */
1182         user_file_update_needed = true;
1183 }
1184
1185
1186 /*
1187  * Rename user
1188  */
1189 void
1190 RenameUser(const char *oldname, const char *newname)
1191 {
1192         HeapTuple       tup;
1193         Relation        rel;
1194
1195         /* ExclusiveLock because we need to update the password file */
1196         rel = heap_openr(ShadowRelationName, ExclusiveLock);
1197
1198         tup = SearchSysCacheCopy(SHADOWNAME,
1199                                                          CStringGetDatum(oldname),
1200                                                          0, 0, 0);
1201         if (!HeapTupleIsValid(tup))
1202                 ereport(ERROR,
1203                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1204                                  errmsg("user \"%s\" does not exist", oldname)));
1205
1206         /*
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
1210          * this and decide.
1211          */
1212         if (((Form_pg_shadow) GETSTRUCT(tup))->usesysid == GetSessionUserId())
1213                 ereport(ERROR,
1214                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1215                                  errmsg("session user may not be renamed")));
1216
1217         /* make sure the new name doesn't exist */
1218         if (SearchSysCacheExists(SHADOWNAME,
1219                                                          CStringGetDatum(newname),
1220                                                          0, 0, 0))
1221                 ereport(ERROR,
1222                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
1223                                  errmsg("user \"%s\" already exists", newname)));
1224
1225         /* must be superuser */
1226         if (!superuser())
1227                 ereport(ERROR,
1228                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1229                                  errmsg("must be superuser to rename users")));
1230
1231         /* rename */
1232         namestrcpy(&(((Form_pg_shadow) GETSTRUCT(tup))->usename), newname);
1233         simple_heap_update(rel, &tup->t_self, tup);
1234         CatalogUpdateIndexes(rel, tup);
1235
1236         heap_close(rel, NoLock);
1237         heap_freetuple(tup);
1238
1239         user_file_update_needed = true;
1240 }
1241
1242
1243 /*
1244  * CheckPgUserAclNotNull
1245  *
1246  * check to see if there is an ACL on pg_shadow
1247  */
1248 static void
1249 CheckPgUserAclNotNull(void)
1250 {
1251         HeapTuple       htup;
1252
1253         htup = SearchSysCache(RELOID,
1254                                                   ObjectIdGetDatum(RelOid_pg_shadow),
1255                                                   0, 0, 0);
1256         if (!HeapTupleIsValid(htup))    /* should not happen, we hope */
1257                 elog(ERROR, "cache lookup failed for relation %u", RelOid_pg_shadow);
1258
1259         if (heap_attisnull(htup, Anum_pg_class_relacl))
1260                 ereport(ERROR,
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)));
1267
1268         ReleaseSysCache(htup);
1269 }
1270
1271
1272
1273 /*
1274  * CREATE GROUP
1275  */
1276 void
1277 CreateGroup(CreateGroupStmt *stmt)
1278 {
1279         Relation        pg_group_rel;
1280         HeapScanDesc scan;
1281         HeapTuple       tuple;
1282         TupleDesc       pg_group_dsc;
1283         bool            group_exists = false,
1284                                 sysid_exists = false,
1285                                 havesysid = false;
1286         int                     max_id;
1287         Datum           new_record[Natts_pg_group];
1288         char            new_record_nulls[Natts_pg_group];
1289         List       *item,
1290                            *option,
1291                            *newlist = NIL;
1292         IdList     *grolist;
1293         int                     sysid = 0;
1294         List       *userElts = NIL;
1295         DefElem    *dsysid = NULL;
1296         DefElem    *duserElts = NULL;
1297
1298         foreach(option, stmt->options)
1299         {
1300                 DefElem    *defel = (DefElem *) lfirst(option);
1301
1302                 if (strcmp(defel->defname, "sysid") == 0)
1303                 {
1304                         if (dsysid)
1305                                 ereport(ERROR,
1306                                                 (errcode(ERRCODE_SYNTAX_ERROR),
1307                                                  errmsg("conflicting or redundant options")));
1308                         dsysid = defel;
1309                 }
1310                 else if (strcmp(defel->defname, "userElts") == 0)
1311                 {
1312                         if (duserElts)
1313                                 ereport(ERROR,
1314                                                 (errcode(ERRCODE_SYNTAX_ERROR),
1315                                                  errmsg("conflicting or redundant options")));
1316                         duserElts = defel;
1317                 }
1318                 else
1319                         elog(ERROR, "option \"%s\" not recognized",
1320                                  defel->defname);
1321         }
1322
1323         if (dsysid)
1324         {
1325                 sysid = intVal(dsysid->arg);
1326                 if (sysid <= 0)
1327                         ereport(ERROR,
1328                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1329                                          errmsg("group ID must be positive")));
1330                 havesysid = true;
1331         }
1332
1333         if (duserElts)
1334                 userElts = (List *) duserElts->arg;
1335
1336         /*
1337          * Make sure the user can do this.
1338          */
1339         if (!superuser())
1340                 ereport(ERROR,
1341                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1342                                  errmsg("must be superuser to create groups")));
1343
1344         if (strcmp(stmt->name, "public") == 0)
1345                 ereport(ERROR,
1346                                 (errcode(ERRCODE_RESERVED_NAME),
1347                                  errmsg("group name \"%s\" is reserved",
1348                                                 stmt->name)));
1349
1350         /*
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.
1355          */
1356         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1357         pg_group_dsc = RelationGetDescr(pg_group_rel);
1358
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)
1363         {
1364                 Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
1365                 int32           this_sysid;
1366
1367                 group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
1368
1369                 this_sysid = group_form->grosysid;
1370                 if (havesysid)                  /* customized id wanted */
1371                         sysid_exists = (this_sysid == sysid);
1372                 else
1373                 {
1374                         /* pick 1 + max */
1375                         if (this_sysid > max_id)
1376                                 max_id = this_sysid;
1377                 }
1378         }
1379         heap_endscan(scan);
1380
1381         if (group_exists)
1382                 ereport(ERROR,
1383                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
1384                                  errmsg("group \"%s\" already exists",
1385                                                 stmt->name)));
1386         if (sysid_exists)
1387                 ereport(ERROR,
1388                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
1389                                  errmsg("group ID %d is already assigned", sysid)));
1390
1391         /* If no sysid given, use max existing id + 1 */
1392         if (!havesysid)
1393                 sysid = max_id + 1;
1394
1395         /*
1396          * Translate the given user names to ids
1397          */
1398         foreach(item, userElts)
1399         {
1400                 const char *groupuser = strVal(lfirst(item));
1401                 int32           userid = get_usesysid(groupuser);
1402
1403                 if (!intMember(userid, newlist))
1404                         newlist = lappendi(newlist, userid);
1405         }
1406
1407         /* build an array to insert */
1408         if (newlist)
1409                 grolist = IdListToArray(newlist);
1410         else
1411                 grolist = NULL;
1412
1413         /*
1414          * Form a tuple to insert
1415          */
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);
1420
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';
1424
1425         tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
1426
1427         /*
1428          * Insert a new record in the pg_group table
1429          */
1430         simple_heap_insert(pg_group_rel, tuple);
1431
1432         /* Update indexes */
1433         CatalogUpdateIndexes(pg_group_rel, tuple);
1434
1435         /*
1436          * Now we can clean up; but keep lock until commit (to avoid possible
1437          * deadlock when commit code tries to acquire lock).
1438          */
1439         heap_close(pg_group_rel, NoLock);
1440
1441         /*
1442          * Set flag to update flat group file at commit.
1443          */
1444         group_file_update_needed = true;
1445 }
1446
1447
1448 /*
1449  * ALTER GROUP
1450  */
1451 void
1452 AlterGroup(AlterGroupStmt *stmt, const char *tag)
1453 {
1454         Relation        pg_group_rel;
1455         TupleDesc       pg_group_dsc;
1456         HeapTuple       group_tuple;
1457         IdList     *oldarray;
1458         Datum           datum;
1459         bool            null;
1460         List       *newlist,
1461                            *item;
1462
1463         /*
1464          * Make sure the user can do this.
1465          */
1466         if (!superuser())
1467                 ereport(ERROR,
1468                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1469                                  errmsg("must be superuser to alter groups")));
1470
1471         /*
1472          * Secure exclusive lock to protect our update of the flat group file.
1473          */
1474         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1475         pg_group_dsc = RelationGetDescr(pg_group_rel);
1476
1477         /*
1478          * Fetch existing tuple for group.
1479          */
1480         group_tuple = SearchSysCache(GRONAME,
1481                                                                  PointerGetDatum(stmt->name),
1482                                                                  0, 0, 0);
1483         if (!HeapTupleIsValid(group_tuple))
1484                 ereport(ERROR,
1485                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1486                                  errmsg("group \"%s\" does not exist", stmt->name)));
1487
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);
1492
1493         /* initialize list with old array contents */
1494         newlist = IdArrayToList(oldarray);
1495
1496         /*
1497          * Now decide what to do.
1498          */
1499         AssertState(stmt->action == +1 || stmt->action == -1);
1500
1501         if (stmt->action == +1)         /* add users, might also be invoked by
1502                                                                  * create user */
1503         {
1504                 /*
1505                  * convert the to be added usernames to sysids and add them to the
1506                  * list
1507                  */
1508                 foreach(item, stmt->listUsers)
1509                 {
1510                         int32           sysid;
1511
1512                         if (strcmp(tag, "ALTER GROUP") == 0)
1513                         {
1514                                 /* Get the uid of the proposed user to add. */
1515                                 sysid = get_usesysid(strVal(lfirst(item)));
1516                         }
1517                         else if (strcmp(tag, "CREATE USER") == 0)
1518                         {
1519                                 /*
1520                                  * in this case we already know the uid and it wouldn't be
1521                                  * in the cache anyway yet
1522                                  */
1523                                 sysid = intVal(lfirst(item));
1524                         }
1525                         else
1526                         {
1527                                 elog(ERROR, "unexpected tag: \"%s\"", tag);
1528                                 sysid = 0;              /* keep compiler quiet */
1529                         }
1530
1531                         if (!intMember(sysid, newlist))
1532                                 newlist = lappendi(newlist, sysid);
1533                 }
1534
1535                 /* Do the update */
1536                 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1537         }                                                       /* endif alter group add user */
1538
1539         else if (stmt->action == -1)    /* drop users from group */
1540         {
1541                 bool            is_dropuser = strcmp(tag, "DROP USER") == 0;
1542
1543                 if (newlist == NIL)
1544                 {
1545                         if (!is_dropuser)
1546                                 ereport(WARNING,
1547                                                 (errcode(ERRCODE_WARNING),
1548                                                  errmsg("group \"%s\" does not have any members",
1549                                                                 stmt->name)));
1550                 }
1551                 else
1552                 {
1553                         /*
1554                          * convert the to be dropped usernames to sysids and remove
1555                          * them from the list
1556                          */
1557                         foreach(item, stmt->listUsers)
1558                         {
1559                                 int32           sysid;
1560
1561                                 if (!is_dropuser)
1562                                 {
1563                                         /* Get the uid of the proposed user to drop. */
1564                                         sysid = get_usesysid(strVal(lfirst(item)));
1565                                 }
1566                                 else
1567                                 {
1568                                         /* for dropuser we already know the uid */
1569                                         sysid = intVal(lfirst(item));
1570                                 }
1571                                 if (intMember(sysid, newlist))
1572                                         newlist = lremovei(sysid, newlist);
1573                                 else if (!is_dropuser)
1574                                         ereport(WARNING,
1575                                                         (errcode(ERRCODE_WARNING),
1576                                                          errmsg("user \"%s\" is not in group \"%s\"",
1577                                                                         strVal(lfirst(item)), stmt->name)));
1578                         }
1579
1580                         /* Do the update */
1581                         UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1582                 }                                               /* endif group not null */
1583         }                                                       /* endif alter group drop user */
1584
1585         ReleaseSysCache(group_tuple);
1586
1587         /*
1588          * Now we can clean up; but keep lock until commit (to avoid possible
1589          * deadlock when commit code tries to acquire lock).
1590          */
1591         heap_close(pg_group_rel, NoLock);
1592
1593         /*
1594          * Set flag to update flat group file at commit.
1595          */
1596         group_file_update_needed = true;
1597 }
1598
1599 /*
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.
1603  */
1604 static void
1605 UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
1606                                           List *members)
1607 {
1608         IdList     *newarray;
1609         Datum           new_record[Natts_pg_group];
1610         char            new_record_nulls[Natts_pg_group];
1611         char            new_record_repl[Natts_pg_group];
1612         HeapTuple       tuple;
1613
1614         newarray = IdListToArray(members);
1615
1616         /*
1617          * Form an updated tuple with the new array and write it back.
1618          */
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));
1622
1623         new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1624         new_record_repl[Anum_pg_group_grolist - 1] = 'r';
1625
1626         tuple = heap_modifytuple(group_tuple, group_rel,
1627                                                   new_record, new_record_nulls, new_record_repl);
1628
1629         simple_heap_update(group_rel, &group_tuple->t_self, tuple);
1630
1631         /* Update indexes */
1632         CatalogUpdateIndexes(group_rel, tuple);
1633 }
1634
1635
1636 /*
1637  * Convert an integer list of sysids to an array.
1638  */
1639 static IdList *
1640 IdListToArray(List *members)
1641 {
1642         int                     nmembers = length(members);
1643         IdList     *newarray;
1644         List       *item;
1645         int                     i;
1646
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 */
1654         i = 0;
1655         foreach(item, members)
1656                 ((int *) ARR_DATA_PTR(newarray))[i++] = lfirsti(item);
1657
1658         return newarray;
1659 }
1660
1661 /*
1662  * Convert an array of sysids to an integer list.
1663  */
1664 static List *
1665 IdArrayToList(IdList *oldarray)
1666 {
1667         List       *newlist = NIL;
1668         int                     hibound,
1669                                 i;
1670
1671         if (oldarray == NULL)
1672                 return NIL;
1673
1674         Assert(ARR_NDIM(oldarray) == 1);
1675         Assert(ARR_ELEMTYPE(oldarray) == INT4OID);
1676
1677         hibound = ARR_DIMS(oldarray)[0];
1678
1679         for (i = 0; i < hibound; i++)
1680         {
1681                 int32           sysid;
1682
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);
1687         }
1688
1689         return newlist;
1690 }
1691
1692
1693 /*
1694  * DROP GROUP
1695  */
1696 void
1697 DropGroup(DropGroupStmt *stmt)
1698 {
1699         Relation        pg_group_rel;
1700         HeapTuple       tuple;
1701
1702         /*
1703          * Make sure the user can do this.
1704          */
1705         if (!superuser())
1706                 ereport(ERROR,
1707                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1708                                  errmsg("must be superuser to drop groups")));
1709
1710         /*
1711          * Secure exclusive lock to protect our update of the flat group file.
1712          */
1713         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1714
1715         /* Find and delete the group. */
1716
1717         tuple = SearchSysCacheCopy(GRONAME,
1718                                                            PointerGetDatum(stmt->name),
1719                                                            0, 0, 0);
1720         if (!HeapTupleIsValid(tuple))
1721                 ereport(ERROR,
1722                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1723                                  errmsg("group \"%s\" does not exist", stmt->name)));
1724
1725         simple_heap_delete(pg_group_rel, &tuple->t_self);
1726
1727         /*
1728          * Now we can clean up; but keep lock until commit (to avoid possible
1729          * deadlock when commit code tries to acquire lock).
1730          */
1731         heap_close(pg_group_rel, NoLock);
1732
1733         /*
1734          * Set flag to update flat group file at commit.
1735          */
1736         group_file_update_needed = true;
1737 }
1738
1739
1740 /*
1741  * Rename group
1742  */
1743 void
1744 RenameGroup(const char *oldname, const char *newname)
1745 {
1746         HeapTuple       tup;
1747         Relation        rel;
1748
1749         /* ExclusiveLock because we need to update the flat group file */
1750         rel = heap_openr(GroupRelationName, ExclusiveLock);
1751
1752         tup = SearchSysCacheCopy(GRONAME,
1753                                                          CStringGetDatum(oldname),
1754                                                          0, 0, 0);
1755         if (!HeapTupleIsValid(tup))
1756                 ereport(ERROR,
1757                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1758                                  errmsg("group \"%s\" does not exist", oldname)));
1759
1760         /* make sure the new name doesn't exist */
1761         if (SearchSysCacheExists(GRONAME,
1762                                                          CStringGetDatum(newname),
1763                                                          0, 0, 0))
1764                 ereport(ERROR,
1765                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
1766                                  errmsg("group \"%s\" already exists", newname)));
1767
1768         /* must be superuser */
1769         if (!superuser())
1770                 ereport(ERROR,
1771                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1772                                  errmsg("must be superuser to rename groups")));
1773
1774         /* rename */
1775         namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
1776         simple_heap_update(rel, &tup->t_self, tup);
1777         CatalogUpdateIndexes(rel, tup);
1778
1779         heap_close(rel, NoLock);
1780         heap_freetuple(tup);
1781
1782         group_file_update_needed = true;
1783 }