]> granicus.if.org Git - postgresql/blob - src/backend/commands/user.c
Merge the last few variable.c configuration variables into the generic
[postgresql] / src / backend / commands / user.c
1 /*-------------------------------------------------------------------------
2  *
3  * user.c
4  *        Commands for manipulating users and groups.
5  *
6  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.101 2002/05/17 01:19:17 tgl Exp $
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
14
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <errno.h>
19 #include <unistd.h>
20
21 #include "access/heapam.h"
22 #include "catalog/catname.h"
23 #include "catalog/pg_database.h"
24 #include "catalog/pg_shadow.h"
25 #include "catalog/pg_group.h"
26 #include "catalog/indexing.h"
27 #include "commands/user.h"
28 #include "libpq/crypt.h"
29 #include "miscadmin.h"
30 #include "storage/pmsignal.h"
31 #include "utils/acl.h"
32 #include "utils/array.h"
33 #include "utils/builtins.h"
34 #include "utils/fmgroids.h"
35 #include "utils/guc.h"
36 #include "utils/lsyscache.h"
37 #include "utils/syscache.h"
38
39
40 extern bool Password_encryption;
41
42 static void CheckPgUserAclNotNull(void);
43 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
44                                                                   List *members);
45 static IdList *IdListToArray(List *members);
46 static List *IdArrayToList(IdList *oldarray);
47
48
49 /*
50  *      fputs_quote
51  *
52  *      Outputs string in quotes, with double-quotes duplicated.
53  *      We could use quote_ident(), but that expects varlena.
54  */
55 static void fputs_quote(char *str, FILE *fp)
56 {
57         fputc('"', fp);
58         while (*str)
59         {
60                 fputc(*str, fp);
61                 if (*str == '"')
62                         fputc('"', fp);
63                 str++;
64         }
65         fputc('"', fp);
66 }
67
68
69
70 /*
71  * group_getfilename --- get full pathname of group file
72  *
73  * Note that result string is palloc'd, and should be freed by the caller.
74  */
75 char *
76 group_getfilename(void)
77 {
78         int                     bufsize;
79         char       *pfnam;
80
81         bufsize = strlen(DataDir) + strlen("/global/") +
82                           strlen(USER_GROUP_FILE) + 1;
83         pfnam = (char *) palloc(bufsize);
84         snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_GROUP_FILE);
85
86         return pfnam;
87 }
88
89
90
91 /*
92  * Get full pathname of password file.
93  * Note that result string is palloc'd, and should be freed by the caller.
94  */
95 char *
96 user_getfilename(void)
97 {
98         int                     bufsize;
99         char       *pfnam;
100
101         bufsize = strlen(DataDir) + strlen("/global/") +
102                           strlen(PWD_FILE) + 1;
103         pfnam = (char *) palloc(bufsize);
104         snprintf(pfnam, bufsize, "%s/global/%s", DataDir, PWD_FILE);
105
106         return pfnam;
107 }
108
109
110
111 /*
112  * write_group_file for trigger update_pg_pwd_and_pg_group
113  */
114 static void
115 write_group_file(Relation urel, Relation grel)
116 {
117         char       *filename,
118                            *tempname;
119         int                     bufsize;
120         FILE       *fp;
121         mode_t          oumask;
122         HeapScanDesc scan;
123         HeapTuple       tuple;
124         TupleDesc       dsc = RelationGetDescr(grel);
125
126         /*
127          * Create a temporary filename to be renamed later.  This prevents the
128          * backend from clobbering the pg_group file while the postmaster might
129          * be reading from it.
130          */
131         filename = group_getfilename();
132         bufsize = strlen(filename) + 12;
133         tempname = (char *) palloc(bufsize);
134
135         snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
136         oumask = umask((mode_t) 077);
137         fp = AllocateFile(tempname, "w");
138         umask(oumask);
139         if (fp == NULL)
140                 elog(ERROR, "write_group_file: unable to write %s: %m", tempname);
141
142         /* read table */
143         scan = heap_beginscan(grel, false, SnapshotSelf, 0, NULL);
144         while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
145         {
146                 Datum           datum, grolist_datum;
147                 bool            isnull;
148                 char       *groname;
149                 IdList     *grolist_p;
150                 AclId      *aidp;
151                 int                     i, j,
152                                         num;
153                 char       *usename;
154                 bool            first_user = true;
155
156                 datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull);
157                 /* ignore NULL groupnames --- shouldn't happen */
158                 if (isnull)
159                         continue;
160                 groname = NameStr(*DatumGetName(datum));
161
162                 /*
163                  * Check for illegal characters in the group name.
164                  */
165                 i = strcspn(groname, "\n");
166                 if (groname[i] != '\0')
167                 {
168                         elog(LOG, "Invalid group name '%s'", groname);
169                         continue;
170                 }
171
172                 grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull);
173                 /* Ignore NULL group lists */
174                 if (isnull)
175                         continue;
176
177                 /* be sure the IdList is not toasted */
178                 grolist_p = DatumGetIdListP(grolist_datum);
179
180                 /* scan grolist */
181                 num = IDLIST_NUM(grolist_p);
182                 aidp = IDLIST_DAT(grolist_p);
183                 for (i = 0; i < num; ++i)
184                 {
185                         tuple = SearchSysCache(SHADOWSYSID,
186                                                                    PointerGetDatum(aidp[i]),
187                                                                    0, 0, 0);
188                         if (HeapTupleIsValid(tuple))
189                         {
190                                 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
191
192                                 /*
193                                  * Check for illegal characters in the user name.
194                                  */
195                                 j = strcspn(usename, "\n");
196                                 if (usename[j] != '\0')
197                                 {
198                                         elog(LOG, "Invalid user name '%s'", usename);
199                                         continue;
200                                 }
201
202                                 /* File format is:
203                                  *              "dbname"        "user1" "user2" "user3"
204                                  */
205                                 if (first_user)
206                                 {
207                                         fputs_quote(groname, fp);
208                                         fputs("\t", fp);
209                                 }
210                                 else
211                                         fputs(" ", fp);
212
213                                 first_user = false;
214                                 fputs_quote(usename, fp);
215
216                                 ReleaseSysCache(tuple);
217                         }
218                 }
219                 if (!first_user)
220                         fputs("\n", fp);
221                 /* if IdList was toasted, free detoasted copy */
222                 if ((Pointer) grolist_p != DatumGetPointer(grolist_datum))
223                         pfree(grolist_p);
224         }
225         heap_endscan(scan);
226
227         fflush(fp);
228         if (ferror(fp))
229                 elog(ERROR, "%s: %m", tempname);
230         FreeFile(fp);
231
232         /*
233          * Rename the temp file to its final name, deleting the old pg_pwd. We
234          * expect that rename(2) is an atomic action.
235          */
236         if (rename(tempname, filename))
237                 elog(ERROR, "rename %s to %s: %m", tempname, filename);
238
239         pfree((void *) tempname);
240         pfree((void *) filename);
241 }
242
243
244
245 /*
246  * write_password_file for trigger update_pg_pwd_and_pg_group
247  *
248  * copy the modified contents of pg_shadow to a file used by the postmaster
249  * for user authentication.  The file is stored as $PGDATA/global/pg_pwd.
250  *
251  * This function set is both a trigger function for direct updates to pg_shadow
252  * as well as being called directly from create/alter/drop user.
253  *
254  * We raise an error to force transaction rollback if we detect an illegal
255  * username or password --- illegal being defined as values that would
256  * mess up the pg_pwd parser.
257  */
258 static void
259 write_user_file(Relation urel)
260 {
261         char       *filename,
262                            *tempname;
263         int                     bufsize;
264         FILE       *fp;
265         mode_t          oumask;
266         HeapScanDesc scan;
267         HeapTuple       tuple;
268         TupleDesc       dsc = RelationGetDescr(urel);
269
270         /*
271          * Create a temporary filename to be renamed later.  This prevents the
272          * backend from clobbering the pg_pwd file while the postmaster might
273          * be reading from it.
274          */
275         filename = user_getfilename();
276         bufsize = strlen(filename) + 12;
277         tempname = (char *) palloc(bufsize);
278
279         snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
280         oumask = umask((mode_t) 077);
281         fp = AllocateFile(tempname, "w");
282         umask(oumask);
283         if (fp == NULL)
284                 elog(ERROR, "write_password_file: unable to write %s: %m", tempname);
285
286         /* read table */
287         scan = heap_beginscan(urel, false, SnapshotSelf, 0, NULL);
288         while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
289         {
290                 Datum           datum;
291                 bool            isnull;
292                 char       *usename,
293                                    *passwd,
294                                    *valuntil;
295                 int                     i;
296
297                 datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull);
298                 /* ignore NULL usernames (shouldn't happen) */
299                 if (isnull)
300                         continue;
301                 usename = NameStr(*DatumGetName(datum));
302
303                 datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull);
304
305                 /*
306                  * It can be argued that people having a null password shouldn't
307                  * be allowed to connect under password authentication, because
308                  * they need to have a password set up first. If you think
309                  * assuming an empty password in that case is better, change this
310                  * logic to look something like the code for valuntil.
311                  */
312                 if (isnull)
313                         continue;
314
315                 passwd = DatumGetCString(DirectFunctionCall1(textout, datum));
316
317                 datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull);
318                 if (isnull)
319                         valuntil = pstrdup("");
320                 else
321                         valuntil = DatumGetCString(DirectFunctionCall1(nabstimeout, datum));
322
323                 /*
324                  * Check for illegal characters in the username and password.
325                  */
326                 i = strcspn(usename, "\n");
327                 if (usename[i] != '\0')
328                         elog(ERROR, "Invalid user name '%s'", usename);
329                 i = strcspn(passwd, "\n");
330                 if (passwd[i] != '\0')
331                         elog(ERROR, "Invalid user password '%s'", passwd);
332
333                 /*
334                  * The extra columns we emit here are not really necessary. To
335                  * remove them, the parser in backend/libpq/crypt.c would need to
336                  * be adjusted.
337                  */
338                 fputs_quote(usename, fp);
339                 fputs(" ", fp);
340                 fputs_quote(passwd, fp);
341                 fputs(" ", fp);
342                 fputs_quote(valuntil, fp);
343                 fputs("\n", fp);
344
345                 pfree(passwd);
346                 pfree(valuntil);
347         }
348         heap_endscan(scan);
349
350         fflush(fp);
351         if (ferror(fp))
352                 elog(ERROR, "%s: %m", tempname);
353         FreeFile(fp);
354
355         /*
356          * Rename the temp file to its final name, deleting the old pg_pwd. We
357          * expect that rename(2) is an atomic action.
358          */
359         if (rename(tempname, filename))
360                 elog(ERROR, "rename %s to %s: %m", tempname, filename);
361
362         pfree((void *) tempname);
363         pfree((void *) filename);
364 }
365
366
367
368 /* This is the wrapper for triggers. */
369 Datum
370 update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS)
371 {
372         /*
373          * ExclusiveLock ensures no one modifies pg_shadow while we read it,
374          * and that only one backend rewrites the flat file at a time.  It's
375          * OK to allow normal reads of pg_shadow in parallel, however.
376          */
377         Relation        urel = heap_openr(ShadowRelationName, ExclusiveLock);
378         Relation        grel = heap_openr(GroupRelationName, ExclusiveLock);
379
380         write_user_file(urel);
381         write_group_file(urel, grel);
382         /* OK to release lock, since we did not modify the relation */
383         heap_close(grel, ExclusiveLock);
384         heap_close(urel, ExclusiveLock);
385
386         /*
387          * Signal the postmaster to reload its password & group-file cache.
388          */
389         SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);
390
391         return PointerGetDatum(NULL);
392 }
393
394
395
396 /*
397  * CREATE USER
398  */
399 void
400 CreateUser(CreateUserStmt *stmt)
401 {
402         Relation        pg_shadow_rel;
403         TupleDesc       pg_shadow_dsc;
404         HeapScanDesc scan;
405         HeapTuple       tuple;
406         Datum           new_record[Natts_pg_shadow];
407         char            new_record_nulls[Natts_pg_shadow];
408         bool            user_exists = false,
409                                 sysid_exists = false,
410                                 havesysid = false;
411         int                     max_id;
412         List       *item,
413                            *option;
414         char       *password = NULL;    /* PostgreSQL user password */
415         bool            encrypt_password = Password_encryption; /* encrypt password? */
416         char            encrypted_password[MD5_PASSWD_LEN + 1];
417         int                     sysid = 0;              /* PgSQL system id (valid if havesysid) */
418         bool            createdb = false;               /* Can the user create databases? */
419         bool            createuser = false;             /* Can this user create users? */
420         List       *groupElts = NIL;    /* The groups the user is a member of */
421         char       *validUntil = NULL;          /* The time the login is valid
422                                                                                  * until */
423         DefElem    *dpassword = NULL;
424         DefElem    *dsysid = NULL;
425         DefElem    *dcreatedb = NULL;
426         DefElem    *dcreateuser = NULL;
427         DefElem    *dgroupElts = NULL;
428         DefElem    *dvalidUntil = NULL;
429
430         /* Extract options from the statement node tree */
431         foreach(option, stmt->options)
432         {
433                 DefElem    *defel = (DefElem *) lfirst(option);
434
435                 if (strcmp(defel->defname, "password") == 0 ||
436                         strcmp(defel->defname, "encryptedPassword") == 0 ||
437                         strcmp(defel->defname, "unencryptedPassword") == 0)
438                 {
439                         if (dpassword)
440                                 elog(ERROR, "CREATE USER: conflicting options");
441                         dpassword = defel;
442                         if (strcmp(defel->defname, "encryptedPassword") == 0)
443                                 encrypt_password = true;
444                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
445                                 encrypt_password = false;
446                 }
447                 else if (strcmp(defel->defname, "sysid") == 0)
448                 {
449                         if (dsysid)
450                                 elog(ERROR, "CREATE USER: conflicting options");
451                         dsysid = defel;
452                 }
453                 else if (strcmp(defel->defname, "createdb") == 0)
454                 {
455                         if (dcreatedb)
456                                 elog(ERROR, "CREATE USER: conflicting options");
457                         dcreatedb = defel;
458                 }
459                 else if (strcmp(defel->defname, "createuser") == 0)
460                 {
461                         if (dcreateuser)
462                                 elog(ERROR, "CREATE USER: conflicting options");
463                         dcreateuser = defel;
464                 }
465                 else if (strcmp(defel->defname, "groupElts") == 0)
466                 {
467                         if (dgroupElts)
468                                 elog(ERROR, "CREATE USER: conflicting options");
469                         dgroupElts = defel;
470                 }
471                 else if (strcmp(defel->defname, "validUntil") == 0)
472                 {
473                         if (dvalidUntil)
474                                 elog(ERROR, "CREATE USER: conflicting options");
475                         dvalidUntil = defel;
476                 }
477                 else
478                         elog(ERROR, "CREATE USER: option \"%s\" not recognized",
479                                  defel->defname);
480         }
481
482         if (dcreatedb)
483                 createdb = intVal(dcreatedb->arg) != 0;
484         if (dcreateuser)
485                 createuser = intVal(dcreateuser->arg) != 0;
486         if (dsysid)
487         {
488                 sysid = intVal(dsysid->arg);
489                 if (sysid <= 0)
490                         elog(ERROR, "user id must be positive");
491                 havesysid = true;
492         }
493         if (dvalidUntil)
494                 validUntil = strVal(dvalidUntil->arg);
495         if (dpassword)
496                 password = strVal(dpassword->arg);
497         if (dgroupElts)
498                 groupElts = (List *) dgroupElts->arg;
499
500         /* Check some permissions first */
501         if (password)
502                 CheckPgUserAclNotNull();
503
504         if (!superuser())
505                 elog(ERROR, "CREATE USER: permission denied");
506
507         if (strcmp(stmt->user, "public") == 0)
508                 elog(ERROR, "CREATE USER: user name \"%s\" is reserved",
509                          stmt->user);
510
511         /*
512          * Scan the pg_shadow relation to be certain the user or id doesn't
513          * already exist.  Note we secure exclusive lock, because we also need
514          * to be sure of what the next usesysid should be, and we need to
515          * protect our update of the flat password file.
516          */
517         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
518         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
519
520         scan = heap_beginscan(pg_shadow_rel, false, SnapshotNow, 0, NULL);
521         max_id = 99;                            /* start auto-assigned ids at 100 */
522         while (!user_exists && !sysid_exists &&
523                    HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
524         {
525                 Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
526                 int32           this_sysid;
527
528                 user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
529
530                 this_sysid = shadow_form->usesysid;
531                 if (havesysid)                  /* customized id wanted */
532                         sysid_exists = (this_sysid == sysid);
533                 else
534                 {
535                         /* pick 1 + max */
536                         if (this_sysid > max_id)
537                                 max_id = this_sysid;
538                 }
539         }
540         heap_endscan(scan);
541
542         if (user_exists)
543                 elog(ERROR, "CREATE USER: user name \"%s\" already exists",
544                          stmt->user);
545         if (sysid_exists)
546                 elog(ERROR, "CREATE USER: sysid %d is already assigned", sysid);
547
548         /* If no sysid given, use max existing id + 1 */
549         if (!havesysid)
550                 sysid = max_id + 1;
551
552         /*
553          * Build a tuple to insert
554          */
555         MemSet(new_record, 0, sizeof(new_record));
556         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
557
558         new_record[Anum_pg_shadow_usename - 1] =
559                 DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
560         new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
561         AssertState(BoolIsValid(createdb));
562         new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
563         new_record[Anum_pg_shadow_usetrace - 1] = BoolGetDatum(false);
564         AssertState(BoolIsValid(createuser));
565         new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
566         /* superuser gets catupd right by default */
567         new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
568
569         if (password)
570         {
571                 if (!encrypt_password || isMD5(password))
572                         new_record[Anum_pg_shadow_passwd - 1] =
573                                 DirectFunctionCall1(textin, CStringGetDatum(password));
574                 else
575                 {
576                         if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
577                                                         encrypted_password))
578                                 elog(ERROR, "CREATE USER: password encryption failed");
579                         new_record[Anum_pg_shadow_passwd - 1] =
580                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
581                 }
582         }
583         else
584                 new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
585
586         if (validUntil)
587                 new_record[Anum_pg_shadow_valuntil - 1] =
588                         DirectFunctionCall1(nabstimein, CStringGetDatum(validUntil));
589         else
590                 new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
591
592         new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
593
594         tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
595
596         /*
597          * Insert new record in the pg_shadow table
598          */
599         heap_insert(pg_shadow_rel, tuple);
600
601         /*
602          * Update indexes
603          */
604         if (RelationGetForm(pg_shadow_rel)->relhasindex)
605         {
606                 Relation        idescs[Num_pg_shadow_indices];
607
608                 CatalogOpenIndices(Num_pg_shadow_indices,
609                                                    Name_pg_shadow_indices, idescs);
610                 CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
611                                                    tuple);
612                 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
613         }
614
615         /*
616          * Add the user to the groups specified. We'll just call the below
617          * AlterGroup for this.
618          */
619         foreach(item, groupElts)
620         {
621                 AlterGroupStmt ags;
622
623                 ags.name = strVal(lfirst(item));                /* the group name to add
624                                                                                                  * this in */
625                 ags.action = +1;
626                 ags.listUsers = makeList1(makeInteger(sysid));
627                 AlterGroup(&ags, "CREATE USER");
628         }
629
630         /*
631          * Now we can clean up; but keep lock until commit.
632          */
633         heap_close(pg_shadow_rel, NoLock);
634
635         /*
636          * Write the updated pg_shadow and pg_group data to the flat file.
637          */
638         update_pg_pwd_and_pg_group(NULL);
639 }
640
641
642
643 /*
644  * ALTER USER
645  */
646 void
647 AlterUser(AlterUserStmt *stmt)
648 {
649         Datum           new_record[Natts_pg_shadow];
650         char            new_record_nulls[Natts_pg_shadow];
651         char            new_record_repl[Natts_pg_shadow];
652         Relation        pg_shadow_rel;
653         TupleDesc       pg_shadow_dsc;
654         HeapTuple       tuple,
655                                 new_tuple;
656         List       *option;
657         char       *password = NULL;    /* PostgreSQL user password */
658         bool            encrypt_password = Password_encryption; /* encrypt password? */
659         char            encrypted_password[MD5_PASSWD_LEN + 1];
660         int                     createdb = -1;  /* Can the user create databases? */
661         int                     createuser = -1;        /* Can this user create users? */
662         char       *validUntil = NULL;          /* The time the login is valid
663                                                                                  * until */
664         DefElem    *dpassword = NULL;
665         DefElem    *dcreatedb = NULL;
666         DefElem    *dcreateuser = NULL;
667         DefElem    *dvalidUntil = NULL;
668
669         /* Extract options from the statement node tree */
670         foreach(option, stmt->options)
671         {
672                 DefElem    *defel = (DefElem *) lfirst(option);
673
674                 if (strcmp(defel->defname, "password") == 0 ||
675                         strcmp(defel->defname, "encryptedPassword") == 0 ||
676                         strcmp(defel->defname, "unencryptedPassword") == 0)
677                 {
678                         if (dpassword)
679                                 elog(ERROR, "ALTER USER: conflicting options");
680                         dpassword = defel;
681                         if (strcmp(defel->defname, "encryptedPassword") == 0)
682                                 encrypt_password = true;
683                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
684                                 encrypt_password = false;
685                 }
686                 else if (strcmp(defel->defname, "createdb") == 0)
687                 {
688                         if (dcreatedb)
689                                 elog(ERROR, "ALTER USER: conflicting options");
690                         dcreatedb = defel;
691                 }
692                 else if (strcmp(defel->defname, "createuser") == 0)
693                 {
694                         if (dcreateuser)
695                                 elog(ERROR, "ALTER USER: conflicting options");
696                         dcreateuser = defel;
697                 }
698                 else if (strcmp(defel->defname, "validUntil") == 0)
699                 {
700                         if (dvalidUntil)
701                                 elog(ERROR, "ALTER USER: conflicting options");
702                         dvalidUntil = defel;
703                 }
704                 else
705                         elog(ERROR, "ALTER USER: option \"%s\" not recognized",
706                                  defel->defname);
707         }
708
709         if (dcreatedb)
710                 createdb = intVal(dcreatedb->arg);
711         if (dcreateuser)
712                 createuser = intVal(dcreateuser->arg);
713         if (dvalidUntil)
714                 validUntil = strVal(dvalidUntil->arg);
715         if (dpassword)
716                 password = strVal(dpassword->arg);
717
718         if (password)
719                 CheckPgUserAclNotNull();
720
721         /* must be superuser or just want to change your own password */
722         if (!superuser() &&
723                 !(createdb < 0 &&
724                   createuser < 0 &&
725                   !validUntil &&
726                   password &&
727                   strcmp(GetUserName(GetUserId()), stmt->user) == 0))
728                 elog(ERROR, "ALTER USER: permission denied");
729
730         /* changes to the flat password file cannot be rolled back */
731         if (IsTransactionBlock() && password)
732                 elog(NOTICE, "ALTER USER: password changes cannot be rolled back");
733
734         /*
735          * Scan the pg_shadow relation to be certain the user exists. Note we
736          * secure exclusive lock to protect our update of the flat password
737          * file.
738          */
739         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
740         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
741
742         tuple = SearchSysCache(SHADOWNAME,
743                                                    PointerGetDatum(stmt->user),
744                                                    0, 0, 0);
745         if (!HeapTupleIsValid(tuple))
746                 elog(ERROR, "ALTER USER: user \"%s\" does not exist", stmt->user);
747
748         /*
749          * Build an updated tuple, perusing the information just obtained
750          */
751         MemSet(new_record, 0, sizeof(new_record));
752         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
753         MemSet(new_record_repl, ' ', sizeof(new_record_repl));
754
755         new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
756                                                                                         CStringGetDatum(stmt->user));
757         new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
758
759         /* createdb */
760         if (createdb >= 0)
761         {
762                 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
763                 new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
764         }
765
766         /*
767          * createuser (superuser) and catupd
768          *
769          * XXX It's rather unclear how to handle catupd.  It's probably best to
770          * keep it equal to the superuser status, otherwise you could end up
771          * with a situation where no existing superuser can alter the
772          * catalogs, including pg_shadow!
773          */
774         if (createuser >= 0)
775         {
776                 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
777                 new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
778
779                 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
780                 new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
781         }
782
783         /* password */
784         if (password)
785         {
786                 if (!encrypt_password || isMD5(password))
787                         new_record[Anum_pg_shadow_passwd - 1] =
788                                 DirectFunctionCall1(textin, CStringGetDatum(password));
789                 else
790                 {
791                         if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
792                                                         encrypted_password))
793                                 elog(ERROR, "CREATE USER: password encryption failed");
794                         new_record[Anum_pg_shadow_passwd - 1] =
795                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
796                 }
797                 new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
798         }
799
800         /* valid until */
801         if (validUntil)
802         {
803                 new_record[Anum_pg_shadow_valuntil - 1] =
804                         DirectFunctionCall1(nabstimein, CStringGetDatum(validUntil));
805                 new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
806         }
807
808         new_tuple = heap_modifytuple(tuple, pg_shadow_rel, new_record,
809                                                                  new_record_nulls, new_record_repl);
810         simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
811
812         /* Update indexes */
813         if (RelationGetForm(pg_shadow_rel)->relhasindex)
814         {
815                 Relation        idescs[Num_pg_shadow_indices];
816
817                 CatalogOpenIndices(Num_pg_shadow_indices,
818                                                    Name_pg_shadow_indices, idescs);
819                 CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
820                                                    new_tuple);
821                 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
822         }
823
824         ReleaseSysCache(tuple);
825         heap_freetuple(new_tuple);
826
827         /*
828          * Now we can clean up.
829          */
830         heap_close(pg_shadow_rel, NoLock);
831
832         /*
833          * Write the updated pg_shadow and pg_group data to the flat file.
834          */
835         update_pg_pwd_and_pg_group(NULL);
836 }
837
838
839 /*
840  * ALTER USER ... SET
841  */
842 void
843 AlterUserSet(AlterUserSetStmt *stmt)
844 {
845         char       *valuestr;
846         HeapTuple       oldtuple,
847                                 newtuple;
848         Relation        rel;
849         Datum           repl_val[Natts_pg_shadow];
850         char            repl_null[Natts_pg_shadow];
851         char            repl_repl[Natts_pg_shadow];
852         int                     i;
853
854         valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
855
856         /*
857          * RowExclusiveLock is sufficient, because we don't need to update
858          * the flat password file.
859          */
860         rel = heap_openr(ShadowRelationName, RowExclusiveLock);
861         oldtuple = SearchSysCache(SHADOWNAME,
862                                                           PointerGetDatum(stmt->user),
863                                                           0, 0, 0);
864         if (!HeapTupleIsValid(oldtuple))
865                 elog(ERROR, "user \"%s\" does not exist", stmt->user);
866
867         if (!(superuser()
868                   || ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
869                 elog(ERROR, "permission denied");
870
871         for (i = 0; i < Natts_pg_shadow; i++)
872                 repl_repl[i] = ' ';
873
874         repl_repl[Anum_pg_shadow_useconfig-1] = 'r';
875         if (strcmp(stmt->variable, "all")==0 && valuestr == NULL)
876                 /* RESET ALL */
877                 repl_null[Anum_pg_shadow_useconfig-1] = 'n';
878         else
879         {
880                 Datum datum;
881                 bool isnull;
882                 ArrayType *array;
883
884                 repl_null[Anum_pg_shadow_useconfig-1] = ' ';
885
886                 datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
887                                                                 Anum_pg_shadow_useconfig, &isnull);
888
889                 array = isnull ? ((ArrayType *) NULL) : DatumGetArrayTypeP(datum);
890
891                 if (valuestr)
892                         array = GUCArrayAdd(array, stmt->variable, valuestr);
893                 else
894                         array = GUCArrayDelete(array, stmt->variable);
895
896                 repl_val[Anum_pg_shadow_useconfig-1] = PointerGetDatum(array);
897         }
898
899         newtuple = heap_modifytuple(oldtuple, rel, repl_val, repl_null, repl_repl);
900         simple_heap_update(rel, &oldtuple->t_self, newtuple);
901
902         {
903                 Relation        idescs[Num_pg_shadow_indices];
904
905                 CatalogOpenIndices(Num_pg_shadow_indices, Name_pg_shadow_indices, idescs);
906                 CatalogIndexInsert(idescs, Num_pg_shadow_indices, rel, newtuple);
907                 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
908         }
909
910         ReleaseSysCache(oldtuple);
911         heap_close(rel, RowExclusiveLock);
912 }
913
914
915
916 /*
917  * DROP USER
918  */
919 void
920 DropUser(DropUserStmt *stmt)
921 {
922         Relation        pg_shadow_rel;
923         TupleDesc       pg_shadow_dsc;
924         List       *item;
925
926         if (!superuser())
927                 elog(ERROR, "DROP USER: permission denied");
928
929         if (IsTransactionBlock())
930                 elog(NOTICE, "DROP USER cannot be rolled back completely");
931
932         /*
933          * Scan the pg_shadow relation to find the usesysid of the user to be
934          * deleted.  Note we secure exclusive lock, because we need to protect
935          * our update of the flat password file.
936          */
937         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
938         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
939
940         foreach(item, stmt->users)
941         {
942                 const char *user = strVal(lfirst(item));
943                 HeapTuple       tuple,
944                                         tmp_tuple;
945                 Relation        pg_rel;
946                 TupleDesc       pg_dsc;
947                 ScanKeyData scankey;
948                 HeapScanDesc scan;
949                 int32           usesysid;
950
951                 tuple = SearchSysCache(SHADOWNAME,
952                                                            PointerGetDatum(user),
953                                                            0, 0, 0);
954                 if (!HeapTupleIsValid(tuple))
955                         elog(ERROR, "DROP USER: user \"%s\" does not exist%s", user,
956                                  (length(stmt->users) > 1) ? " (no users removed)" : "");
957
958                 usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
959
960                 if (usesysid == GetUserId())
961                         elog(ERROR, "current user cannot be dropped");
962                 if (usesysid == GetSessionUserId())
963                         elog(ERROR, "session user cannot be dropped");
964
965                 /*
966                  * Check if user still owns a database. If so, error out.
967                  *
968                  * (It used to be that this function would drop the database
969                  * automatically. This is not only very dangerous for people that
970                  * don't read the manual, it doesn't seem to be the behaviour one
971                  * would expect either.) -- petere 2000/01/14)
972                  */
973                 pg_rel = heap_openr(DatabaseRelationName, AccessShareLock);
974                 pg_dsc = RelationGetDescr(pg_rel);
975
976                 ScanKeyEntryInitialize(&scankey, 0x0,
977                                                            Anum_pg_database_datdba, F_INT4EQ,
978                                                            Int32GetDatum(usesysid));
979
980                 scan = heap_beginscan(pg_rel, false, SnapshotNow, 1, &scankey);
981
982                 if (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
983                 {
984                         char       *dbname;
985
986                         dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
987                         elog(ERROR, "DROP USER: user \"%s\" owns database \"%s\", cannot be removed%s",
988                                  user, dbname,
989                                  (length(stmt->users) > 1) ? " (no users removed)" : "");
990                 }
991
992                 heap_endscan(scan);
993                 heap_close(pg_rel, AccessShareLock);
994
995                 /*
996                  * Somehow we'd have to check for tables, views, etc. owned by the
997                  * user as well, but those could be spread out over all sorts of
998                  * databases which we don't have access to (easily).
999                  */
1000
1001                 /*
1002                  * Remove the user from the pg_shadow table
1003                  */
1004                 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
1005
1006                 ReleaseSysCache(tuple);
1007
1008                 /*
1009                  * Remove user from groups
1010                  *
1011                  * try calling alter group drop user for every group
1012                  */
1013                 pg_rel = heap_openr(GroupRelationName, ExclusiveLock);
1014                 pg_dsc = RelationGetDescr(pg_rel);
1015                 scan = heap_beginscan(pg_rel, false, SnapshotNow, 0, NULL);
1016                 while (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
1017                 {
1018                         AlterGroupStmt ags;
1019
1020                         /* the group name from which to try to drop the user: */
1021                         ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
1022                         ags.action = -1;
1023                         ags.listUsers = makeList1(makeInteger(usesysid));
1024                         AlterGroup(&ags, "DROP USER");
1025                 }
1026                 heap_endscan(scan);
1027                 heap_close(pg_rel, ExclusiveLock);
1028
1029                 /*
1030                  * Advance command counter so that later iterations of this loop
1031                  * will see the changes already made.  This is essential if, for
1032                  * example, we are trying to drop two users who are members of the
1033                  * same group --- the AlterGroup for the second user had better
1034                  * see the tuple updated from the first one.
1035                  */
1036                 CommandCounterIncrement();
1037         }
1038
1039         /*
1040          * Now we can clean up.
1041          */
1042         heap_close(pg_shadow_rel, NoLock);
1043
1044         /*
1045          * Write the updated pg_shadow and pg_group data to the flat file.
1046          */
1047         update_pg_pwd_and_pg_group(NULL);
1048 }
1049
1050
1051
1052 /*
1053  * CheckPgUserAclNotNull
1054  *
1055  * check to see if there is an ACL on pg_shadow
1056  */
1057 static void
1058 CheckPgUserAclNotNull(void)
1059 {
1060         HeapTuple       htup;
1061
1062         htup = SearchSysCache(RELOID,
1063                                                   ObjectIdGetDatum(RelOid_pg_shadow),
1064                                                   0, 0, 0);
1065         if (!HeapTupleIsValid(htup))
1066                 elog(ERROR, "CheckPgUserAclNotNull: \"%s\" not found",
1067                          ShadowRelationName);
1068
1069         if (heap_attisnull(htup, Anum_pg_class_relacl))
1070                 elog(ERROR,
1071                          "To use passwords, you have to revoke permissions on %s "
1072                          "so normal users cannot read the passwords. "
1073                          "Try 'REVOKE ALL ON \"%s\" FROM PUBLIC'.",
1074                          ShadowRelationName, ShadowRelationName);
1075
1076         ReleaseSysCache(htup);
1077 }
1078
1079
1080
1081 /*
1082  * CREATE GROUP
1083  */
1084 void
1085 CreateGroup(CreateGroupStmt *stmt)
1086 {
1087         Relation        pg_group_rel;
1088         HeapScanDesc scan;
1089         HeapTuple       tuple;
1090         TupleDesc       pg_group_dsc;
1091         bool            group_exists = false,
1092                                 sysid_exists = false,
1093                                 havesysid = false;
1094         int                     max_id;
1095         Datum           new_record[Natts_pg_group];
1096         char            new_record_nulls[Natts_pg_group];
1097         List       *item,
1098                            *option,
1099                            *newlist = NIL;
1100         IdList     *grolist;
1101         int                     sysid = 0;
1102         List       *userElts = NIL;
1103         DefElem    *dsysid = NULL;
1104         DefElem    *duserElts = NULL;
1105
1106         foreach(option, stmt->options)
1107         {
1108                 DefElem    *defel = (DefElem *) lfirst(option);
1109
1110                 if (strcmp(defel->defname, "sysid") == 0)
1111                 {
1112                         if (dsysid)
1113                                 elog(ERROR, "CREATE GROUP: conflicting options");
1114                         dsysid = defel;
1115                 }
1116                 else if (strcmp(defel->defname, "userElts") == 0)
1117                 {
1118                         if (duserElts)
1119                                 elog(ERROR, "CREATE GROUP: conflicting options");
1120                         duserElts = defel;
1121                 }
1122                 else
1123                         elog(ERROR, "CREATE GROUP: option \"%s\" not recognized",
1124                                  defel->defname);
1125         }
1126
1127         if (dsysid)
1128         {
1129                 sysid = intVal(dsysid->arg);
1130                 if (sysid <= 0)
1131                         elog(ERROR, "group id must be positive");
1132                 havesysid = true;
1133         }
1134
1135         if (duserElts)
1136                 userElts = (List *) duserElts->arg;
1137
1138         /*
1139          * Make sure the user can do this.
1140          */
1141         if (!superuser())
1142                 elog(ERROR, "CREATE GROUP: permission denied");
1143
1144         if (strcmp(stmt->name, "public") == 0)
1145                 elog(ERROR, "CREATE GROUP: group name \"%s\" is reserved",
1146                          stmt->name);
1147
1148         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1149         pg_group_dsc = RelationGetDescr(pg_group_rel);
1150
1151         scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
1152         max_id = 99;                            /* start auto-assigned ids at 100 */
1153         while (!group_exists && !sysid_exists &&
1154                    HeapTupleIsValid(tuple = heap_getnext(scan, false)))
1155         {
1156                 Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
1157                 int32           this_sysid;
1158
1159                 group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
1160
1161                 this_sysid = group_form->grosysid;
1162                 if (havesysid)                  /* customized id wanted */
1163                         sysid_exists = (this_sysid == sysid);
1164                 else
1165                 {
1166                         /* pick 1 + max */
1167                         if (this_sysid > max_id)
1168                                 max_id = this_sysid;
1169                 }
1170         }
1171         heap_endscan(scan);
1172
1173         if (group_exists)
1174                 elog(ERROR, "CREATE GROUP: group name \"%s\" already exists",
1175                          stmt->name);
1176         if (sysid_exists)
1177                 elog(ERROR, "CREATE GROUP: group sysid %d is already assigned",
1178                          sysid);
1179
1180         if (!havesysid)
1181                 sysid = max_id + 1;
1182
1183         /*
1184          * Translate the given user names to ids
1185          */
1186         foreach(item, userElts)
1187         {
1188                 const char *groupuser = strVal(lfirst(item));
1189                 int32           userid = get_usesysid(groupuser);
1190
1191                 if (!intMember(userid, newlist))
1192                         newlist = lappendi(newlist, userid);
1193         }
1194
1195         /* build an array to insert */
1196         if (newlist)
1197                 grolist = IdListToArray(newlist);
1198         else
1199                 grolist = NULL;
1200
1201         /*
1202          * Form a tuple to insert
1203          */
1204         new_record[Anum_pg_group_groname - 1] =
1205                 DirectFunctionCall1(namein, CStringGetDatum(stmt->name));
1206         new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid);
1207         new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist);
1208
1209         new_record_nulls[Anum_pg_group_groname - 1] = ' ';
1210         new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
1211         new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
1212
1213         tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
1214
1215         /*
1216          * Insert a new record in the pg_group_table
1217          */
1218         heap_insert(pg_group_rel, tuple);
1219
1220         /*
1221          * Update indexes
1222          */
1223         if (RelationGetForm(pg_group_rel)->relhasindex)
1224         {
1225                 Relation        idescs[Num_pg_group_indices];
1226
1227                 CatalogOpenIndices(Num_pg_group_indices,
1228                                                    Name_pg_group_indices, idescs);
1229                 CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
1230                                                    tuple);
1231                 CatalogCloseIndices(Num_pg_group_indices, idescs);
1232         }
1233
1234         heap_close(pg_group_rel, NoLock);
1235
1236         /*
1237          * Write the updated pg_shadow and pg_group data to the flat file.
1238          */
1239         update_pg_pwd_and_pg_group(NULL);
1240 }
1241
1242
1243
1244 /*
1245  * ALTER GROUP
1246  */
1247 void
1248 AlterGroup(AlterGroupStmt *stmt, const char *tag)
1249 {
1250         Relation        pg_group_rel;
1251         TupleDesc       pg_group_dsc;
1252         HeapTuple       group_tuple;
1253         IdList     *oldarray;
1254         Datum           datum;
1255         bool            null;
1256         List       *newlist,
1257                            *item;
1258
1259         /*
1260          * Make sure the user can do this.
1261          */
1262         if (!superuser())
1263                 elog(ERROR, "%s: permission denied", tag);
1264
1265         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1266         pg_group_dsc = RelationGetDescr(pg_group_rel);
1267
1268         /*
1269          * Fetch existing tuple for group.
1270          */
1271         group_tuple = SearchSysCache(GRONAME,
1272                                                                  PointerGetDatum(stmt->name),
1273                                                                  0, 0, 0);
1274         if (!HeapTupleIsValid(group_tuple))
1275                 elog(ERROR, "%s: group \"%s\" does not exist", tag, stmt->name);
1276
1277         /* Fetch old group membership. */
1278         datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
1279                                                  pg_group_dsc, &null);
1280         oldarray = null ? ((IdList *) NULL) : DatumGetIdListP(datum);
1281
1282         /* initialize list with old array contents */
1283         newlist = IdArrayToList(oldarray);
1284
1285         /*
1286          * Now decide what to do.
1287          */
1288         AssertState(stmt->action == +1 || stmt->action == -1);
1289
1290         if (stmt->action == +1)         /* add users, might also be invoked by
1291                                                                  * create user */
1292         {
1293                 /*
1294                  * convert the to be added usernames to sysids and add them to
1295                  * the list
1296                  */
1297                 foreach(item, stmt->listUsers)
1298                 {
1299                         int32   sysid;
1300
1301                         if (strcmp(tag, "ALTER GROUP") == 0)
1302                         {
1303                                 /* Get the uid of the proposed user to add. */
1304                                 sysid = get_usesysid(strVal(lfirst(item)));
1305                         }
1306                         else if (strcmp(tag, "CREATE USER") == 0)
1307                         {
1308                                 /*
1309                                  * in this case we already know the uid and it wouldn't be
1310                                  * in the cache anyway yet
1311                                  */
1312                                 sysid = intVal(lfirst(item));
1313                         }
1314                         else
1315                         {
1316                                 elog(ERROR, "AlterGroup: unknown tag %s", tag);
1317                                 sysid = 0;              /* keep compiler quiet */
1318                         }
1319
1320                         if (!intMember(sysid, newlist))
1321                                 newlist = lappendi(newlist, sysid);
1322                         else
1323                                 /*
1324                                  * we silently assume here that this error will only come
1325                                  * up in a ALTER GROUP statement
1326                                  */
1327                                 elog(WARNING, "%s: user \"%s\" is already in group \"%s\"",
1328                                          tag, strVal(lfirst(item)), stmt->name);
1329                 }
1330
1331                 /* Do the update */
1332                 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1333         }                                                       /* endif alter group add user */
1334
1335         else if (stmt->action == -1)    /* drop users from group */
1336         {
1337                 bool            is_dropuser = strcmp(tag, "DROP USER") == 0;
1338
1339                 if (newlist == NIL)
1340                 {
1341                         if (!is_dropuser)
1342                                 elog(WARNING, "ALTER GROUP: group \"%s\" does not have any members", stmt->name);
1343                 }
1344                 else
1345                 {
1346                         /*
1347                          * convert the to be dropped usernames to sysids and
1348                          * remove them from the list
1349                          */
1350                         foreach(item, stmt->listUsers)
1351                         {
1352                                 int32           sysid;
1353
1354                                 if (!is_dropuser)
1355                                 {
1356                                         /* Get the uid of the proposed user to drop. */
1357                                         sysid = get_usesysid(strVal(lfirst(item)));
1358                                 }
1359                                 else
1360                                 {
1361                                         /* for dropuser we already know the uid */
1362                                         sysid = intVal(lfirst(item));
1363                                 }
1364                                 if (intMember(sysid, newlist))
1365                                         newlist = lremovei(sysid, newlist);
1366                                 else if (!is_dropuser)
1367                                         elog(WARNING, "ALTER GROUP: user \"%s\" is not in group \"%s\"", strVal(lfirst(item)), stmt->name);
1368                         }
1369
1370                         /* Do the update */
1371                         UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1372                 }                                               /* endif group not null */
1373         }                                                       /* endif alter group drop user */
1374
1375         ReleaseSysCache(group_tuple);
1376
1377         /*
1378          * Write the updated pg_shadow and pg_group data to the flat files.
1379          */
1380         heap_close(pg_group_rel, NoLock);
1381
1382         /*
1383          * Write the updated pg_shadow and pg_group data to the flat file.
1384          */
1385         update_pg_pwd_and_pg_group(NULL);
1386 }
1387
1388 /*
1389  * Subroutine for AlterGroup: given a pg_group tuple and a desired new
1390  * membership (expressed as an integer list), form and write an updated tuple.
1391  * The pg_group relation must be open and locked already.
1392  */
1393 static void
1394 UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
1395                                           List *members)
1396 {
1397         IdList     *newarray;
1398         Datum           new_record[Natts_pg_group];
1399         char            new_record_nulls[Natts_pg_group];
1400         char            new_record_repl[Natts_pg_group];
1401         HeapTuple       tuple;
1402
1403         newarray = IdListToArray(members);
1404
1405         /*
1406          * Form an updated tuple with the new array and write it back.
1407          */
1408         MemSet(new_record, 0, sizeof(new_record));
1409         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1410         MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1411
1412         new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1413         new_record_repl[Anum_pg_group_grolist - 1] = 'r';
1414
1415         tuple = heap_modifytuple(group_tuple, group_rel,
1416                                                          new_record, new_record_nulls, new_record_repl);
1417
1418         simple_heap_update(group_rel, &group_tuple->t_self, tuple);
1419
1420         /* Update indexes */
1421         if (RelationGetForm(group_rel)->relhasindex)
1422         {
1423                 Relation        idescs[Num_pg_group_indices];
1424
1425                 CatalogOpenIndices(Num_pg_group_indices,
1426                                                    Name_pg_group_indices, idescs);
1427                 CatalogIndexInsert(idescs, Num_pg_group_indices, group_rel,
1428                                                    tuple);
1429                 CatalogCloseIndices(Num_pg_group_indices, idescs);
1430         }
1431 }
1432
1433
1434 /*
1435  * Convert an integer list of sysids to an array.
1436  */
1437 static IdList *
1438 IdListToArray(List *members)
1439 {
1440         int                     nmembers = length(members);
1441         IdList     *newarray;
1442         List       *item;
1443         int                     i;
1444
1445         newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
1446         newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
1447         newarray->flags = 0;
1448         ARR_NDIM(newarray) = 1;         /* one dimensional array */
1449         ARR_LBOUND(newarray)[0] = 1;    /* axis starts at one */
1450         ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */
1451         i = 0;
1452         foreach(item, members)
1453         {
1454                 ((int *) ARR_DATA_PTR(newarray))[i++] = lfirsti(item);
1455         }
1456
1457         return newarray;
1458 }
1459
1460 /*
1461  * Convert an array of sysids to an integer list.
1462  */
1463 static List *
1464 IdArrayToList(IdList *oldarray)
1465 {
1466         List       *newlist = NIL;
1467         int                     hibound,
1468                                 i;
1469
1470         if (oldarray == NULL)
1471                 return NIL;
1472
1473         Assert(ARR_NDIM(oldarray) == 1);
1474
1475         hibound = ARR_DIMS(oldarray)[0];
1476
1477         for (i = 0; i < hibound; i++)
1478         {
1479                 int32           sysid;
1480
1481                 sysid = ((int *) ARR_DATA_PTR(oldarray))[i];
1482                 /* filter out any duplicates --- probably a waste of time */
1483                 if (!intMember(sysid, newlist))
1484                         newlist = lappendi(newlist, sysid);
1485         }
1486
1487         return newlist;
1488 }
1489
1490
1491 /*
1492  * DROP GROUP
1493  */
1494 void
1495 DropGroup(DropGroupStmt *stmt)
1496 {
1497         Relation        pg_group_rel;
1498         HeapTuple       tuple;
1499
1500         /*
1501          * Make sure the user can do this.
1502          */
1503         if (!superuser())
1504                 elog(ERROR, "DROP GROUP: permission denied");
1505
1506         /*
1507          * Drop the group.
1508          */
1509         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1510
1511         tuple = SearchSysCacheCopy(GRONAME,
1512                                                            PointerGetDatum(stmt->name),
1513                                                            0, 0, 0);
1514         if (!HeapTupleIsValid(tuple))
1515                 elog(ERROR, "DROP GROUP: group \"%s\" does not exist", stmt->name);
1516
1517         simple_heap_delete(pg_group_rel, &tuple->t_self);
1518
1519         heap_close(pg_group_rel, NoLock);
1520
1521         /*
1522          * Write the updated pg_shadow and pg_group data to the flat file.
1523          */
1524         update_pg_pwd_and_pg_group(NULL);
1525 }