]> granicus.if.org Git - postgresql/blob - src/backend/commands/user.c
Here's the Create/Alter/Drop Group stuff that's been really overdue. I
[postgresql] / src / backend / commands / user.c
1 /*-------------------------------------------------------------------------
2  *
3  * user.c
4  *        use pg_exec_query to create a new user in the catalog
5  *
6  * Copyright (c) 1994, Regents of the University of California
7  *
8  * $Id: user.c,v 1.45 1999/12/16 17:24:13 momjian Exp $
9  *
10  *-------------------------------------------------------------------------
11  */
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16
17 #include "postgres.h"
18
19 #include "access/heapam.h"
20 #include "catalog/catname.h"
21 #include "catalog/pg_database.h"
22 #include "catalog/pg_shadow.h"
23 #include "catalog/pg_group.h"
24 #include "catalog/indexing.h"
25 #include "commands/copy.h"
26 #include "commands/user.h"
27 #include "libpq/crypt.h"
28 #include "miscadmin.h"
29 #include "nodes/pg_list.h"
30 #include "tcop/tcopprot.h"
31 #include "utils/acl.h"
32 #include "utils/array.h"
33 #include "utils/syscache.h"
34
35 static void CheckPgUserAclNotNull(void);
36
37 #define SQL_LENGTH      512
38
39 /*---------------------------------------------------------------------
40  * UpdatePgPwdFile
41  *
42  * copy the modified contents of pg_shadow to a file used by the postmaster
43  * for user authentication.  The file is stored as $PGDATA/pg_pwd.
44  *
45  * NB: caller is responsible for ensuring that only one backend can
46  * execute this routine at a time.  Acquiring AccessExclusiveLock on
47  * pg_shadow is the standard way to do that.
48  *---------------------------------------------------------------------
49  */
50
51 /* This is the old name. Now uses a lower case name to be able to call this
52    from SQL. */
53 #define UpdatePgPwdFile() update_pg_pwd()
54
55 void
56 update_pg_pwd()
57 {
58         char       *filename,
59                            *tempname;
60         int                     bufsize;
61
62         /*
63          * Create a temporary filename to be renamed later.  This prevents the
64          * backend from clobbering the pg_pwd file while the postmaster might
65          * be reading from it.
66          */
67         filename = crypt_getpwdfilename();
68         bufsize = strlen(filename) + 12;
69         tempname = (char *) palloc(bufsize);
70         snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
71
72         /*
73          * Copy the contents of pg_shadow to the pg_pwd ASCII file using the
74          * SEPCHAR character as the delimiter between fields.  Make sure the
75          * file is created with mode 600 (umask 077).
76          */
77         DoCopy(ShadowRelationName,      /* relname */
78                    false,                               /* binary */
79                    false,                               /* oids */
80                    false,                               /* from */
81                    false,                               /* pipe */
82                    tempname,                    /* filename */
83                    CRYPT_PWD_FILE_SEPSTR, /* delim */
84            "",                  /* nulls */
85                    0077);                               /* fileumask */
86         /*
87          * And rename the temp file to its final name, deleting the old pg_pwd.
88          */
89         rename(tempname, filename);
90
91         /*
92          * Create a flag file the postmaster will detect the next time it
93          * tries to authenticate a user.  The postmaster will know to reload
94          * the pg_pwd file contents.
95          */
96         filename = crypt_getpwdreloadfilename();
97         creat(filename, S_IRUSR | S_IWUSR);
98
99         pfree((void *) tempname);
100 }
101
102 /*---------------------------------------------------------------------
103  * DefineUser
104  *
105  * Add the user to the pg_shadow relation, and if specified make sure the
106  * user is specified in the desired groups of defined in pg_group.
107  *---------------------------------------------------------------------
108  */
109 void
110 DefineUser(CreateUserStmt *stmt, CommandDest dest)
111 {
112         char       *pg_shadow,
113                                 sql[SQL_LENGTH];
114         Relation        pg_shadow_rel;
115         TupleDesc       pg_shadow_dsc;
116         HeapScanDesc scan;
117         HeapTuple       tuple;
118         bool            user_exists = false,
119                 sysid_exists = false,
120                                 inblock,
121                 havesysid,
122                                 havepassword,
123                                 havevaluntil;
124         int                     max_id = -1;
125     List       *item;
126
127     havesysid    = stmt->sysid >= 0;
128         havepassword = stmt->password && stmt->password[0];
129         havevaluntil = stmt->validUntil && stmt->validUntil[0];
130
131         if (havepassword)
132                 CheckPgUserAclNotNull();
133         if (!(inblock = IsTransactionBlock()))
134                 BeginTransactionBlock();
135
136         /*
137          * Make sure the user attempting to create a user can insert into the
138          * pg_shadow relation.
139          */
140         pg_shadow = GetPgUserName();
141         if (pg_aclcheck(ShadowRelationName, pg_shadow, ACL_RD | ACL_WR | ACL_AP) != ACLCHECK_OK)
142         {
143                 UserAbortTransactionBlock();
144                 elog(ERROR, "DefineUser: user \"%s\" does not have SELECT and INSERT privilege for \"%s\"",
145                          pg_shadow, ShadowRelationName);
146                 return;
147         }
148
149         /*
150          * Scan the pg_shadow relation to be certain the user or id doesn't already
151          * exist.  Note we secure exclusive lock, because we also need to be
152          * sure of what the next usesysid should be, and we need to protect
153          * our update of the flat password file.
154          */
155         pg_shadow_rel = heap_openr(ShadowRelationName, AccessExclusiveLock);
156         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
157
158         scan = heap_beginscan(pg_shadow_rel, false, SnapshotNow, 0, NULL);
159         while (!user_exists && !sysid_exists && HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
160         {
161         Datum           datum;
162         bool        null;
163
164                 datum = heap_getattr(tuple, Anum_pg_shadow_usename, pg_shadow_dsc, &null);
165                 user_exists = datum && !null && (strcmp((char *) datum, stmt->user) == 0);
166
167                 datum = heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null);
168         if (havesysid) /* customized id wanted */
169             sysid_exists = datum && !null && ((int)datum == stmt->sysid);
170         else /* pick 1 + max */
171         {
172             if ((int) datum > max_id)
173                 max_id = (int) datum;
174         }
175         }
176         heap_endscan(scan);
177
178         if (user_exists || sysid_exists)
179         {
180                 heap_close(pg_shadow_rel, AccessExclusiveLock);
181                 UserAbortTransactionBlock();
182         if (user_exists)
183             elog(ERROR, "DefineUser: user name \"%s\" already exists", stmt->user);
184         else
185             elog(ERROR, "DefineUser: sysid %d is already assigned", stmt->sysid);
186                 return;
187         }
188
189         /*
190          * Build the insert statement to be executed.
191          *
192          * XXX Ugly as this code is, it still fails to cope with ' or \ in any of
193          * the provided strings.
194          *
195          * XXX This routine would be *lots* better if it inserted the new
196          * tuple with formtuple/heap_insert.  For one thing, all of the
197          * transaction-block gamesmanship could be eliminated, because
198          * it's only there to make the world safe for a recursive call
199          * to pg_exec_query_dest().
200          */
201         snprintf(sql, SQL_LENGTH,
202                          "insert into %s (usename,usesysid,usecreatedb,usetrace,"
203                          "usesuper,usecatupd,passwd,valuntil) "
204                          "values('%s',%d,'%c','f','%c','%c',%s%s%s,%s%s%s)",
205                          ShadowRelationName,
206                          stmt->user,
207                          havesysid ? stmt->sysid : max_id + 1,
208                          (stmt->createdb && *stmt->createdb) ? 't' : 'f',
209                          (stmt->createuser && *stmt->createuser) ? 't' : 'f',
210                          ((stmt->createdb && *stmt->createdb) ||
211                           (stmt->createuser && *stmt->createuser)) ? 't' : 'f',
212                          havepassword ? "'" : "",
213                          havepassword ? stmt->password : "NULL",
214                          havepassword ? "'" : "",
215                          havevaluntil ? "'" : "",
216                          havevaluntil ? stmt->validUntil : "NULL",
217                          havevaluntil ? "'" : "");
218
219         /*
220          * XXX If insert fails, say because a bogus valuntil date is given,
221          * need to catch the resulting error and undo our transaction.
222          */
223         pg_exec_query_dest(sql, dest, false);
224
225         /*
226          * Add the user to the groups specified. We'll just call the below
227      * AlterGroup for this.
228          */
229     foreach(item, stmt->groupElts)
230     {
231         AlterGroupStmt ags;
232
233         ags.name = strVal(lfirst(item));
234         ags.action = +1;
235         ags.listUsers = lcons((void*)makeString(stmt->user), NIL);
236         AlterGroup(&ags, dest);
237     }
238
239         /*
240          * Write the updated pg_shadow data to the flat password file.
241          * Because we are still holding AccessExclusiveLock on pg_shadow,
242          * we can be sure no other backend will try to write the flat
243          * file at the same time.
244          */
245         UpdatePgPwdFile();
246
247         /*
248          * Now we can clean up.
249          */
250         heap_close(pg_shadow_rel, AccessExclusiveLock);
251
252         if (IsTransactionBlock() && !inblock)
253                 EndTransactionBlock();
254 }
255
256
257 extern void
258 AlterUser(AlterUserStmt *stmt, CommandDest dest)
259 {
260
261         char       *pg_shadow,
262                                 sql[SQL_LENGTH];
263         Relation        pg_shadow_rel;
264         TupleDesc       pg_shadow_dsc;
265         HeapTuple       tuple;
266         bool            inblock;
267     bool        comma = false;
268
269         if (stmt->password)
270                 CheckPgUserAclNotNull();
271         if (!(inblock = IsTransactionBlock()))
272                 BeginTransactionBlock();
273
274         /*
275          * Make sure the user attempting to create a user can insert into the
276          * pg_shadow relation.
277          */
278         pg_shadow = GetPgUserName();
279         if (pg_aclcheck(ShadowRelationName, pg_shadow, ACL_RD | ACL_WR) != ACLCHECK_OK)
280         {
281                 UserAbortTransactionBlock();
282                 elog(ERROR, "AlterUser: user \"%s\" does not have SELECT and UPDATE privilege for \"%s\"",
283                          pg_shadow, ShadowRelationName);
284                 return;
285         }
286
287         /*
288          * Scan the pg_shadow relation to be certain the user exists.
289          * Note we secure exclusive lock to protect our update of the
290          * flat password file.
291          */
292         pg_shadow_rel = heap_openr(ShadowRelationName, AccessExclusiveLock);
293         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
294
295         tuple = SearchSysCacheTuple(SHADOWNAME,
296                                                                 PointerGetDatum(stmt->user),
297                                                                 0, 0, 0);
298         if (!HeapTupleIsValid(tuple))
299         {
300                 heap_close(pg_shadow_rel, AccessExclusiveLock);
301                 UserAbortTransactionBlock();
302                 elog(ERROR, "AlterUser: user \"%s\" does not exist", stmt->user);
303         }
304
305     /* look for duplicate sysid */
306         tuple = SearchSysCacheTuple(SHADOWSYSID,
307                                                                 Int32GetDatum(stmt->sysid),
308                                                                 0, 0, 0);
309     if (HeapTupleIsValid(tuple))
310     {
311         Datum datum;
312         bool null;
313
314                 datum = heap_getattr(tuple, Anum_pg_shadow_usename, pg_shadow_dsc, &null);
315         if (datum && !null && strcmp((char *) datum, stmt->user) != 0)
316         {
317             heap_close(pg_shadow_rel, AccessExclusiveLock);
318             UserAbortTransactionBlock();
319             elog(ERROR, "AlterUser: sysid %d is already assigned", stmt->sysid);
320         }
321     }
322
323
324         /*
325          * Create the update statement to modify the user.
326          *
327          * XXX see diatribe in preceding routine.  This code is just as bogus.
328          */
329         snprintf(sql, SQL_LENGTH, "update %s set ", ShadowRelationName);
330
331         if (stmt->password)
332     {
333                 snprintf(sql + strlen(sql), SQL_LENGTH - strlen(sql),
334                                  "passwd = '%s'", stmt->password);
335         comma = true;
336     }
337
338     if (stmt->sysid>=0)
339     {
340         if (comma)
341             strcat(sql, ", ");
342         snprintf(sql + strlen(sql), SQL_LENGTH - strlen(sql),
343                  "usesysid = %d", stmt->sysid);
344         comma = true;
345     }
346
347         if (stmt->createdb)
348     {
349         if (comma)
350             strcat(sql, ", ");
351                 snprintf(sql + strlen(sql), SQL_LENGTH - strlen(sql),
352                                  "usecreatedb='%c'",
353                                  *stmt->createdb ? 't' : 'f');
354         comma = true;
355     }
356
357         if (stmt->createuser)
358     {
359         if (comma)
360             strcat(sql, ", ");
361                 snprintf(sql + strlen(sql), SQL_LENGTH - strlen(sql),
362                                  "usesuper='%c'",
363                                  *stmt->createuser ? 't' : 'f');
364         comma = true;
365     }
366
367         if (stmt->validUntil)
368     {
369         if (comma)
370             strcat(sql, ", ");
371                 snprintf(sql + strlen(sql), SQL_LENGTH - strlen(sql),
372                                  "valuntil='%s'",
373                                  stmt->validUntil);
374     }
375
376         snprintf(sql + strlen(sql), SQL_LENGTH - strlen(sql),
377                          " where usename = '%s'",
378                          stmt->user);
379
380         pg_exec_query_dest(sql, dest, false);
381
382         /*
383          * Add stuff here for groups?
384          */
385     if (stmt->groupElts)
386         elog(NOTICE, "IN GROUP is not implemented for ALTER USER.");
387
388         /*
389          * Write the updated pg_shadow data to the flat password file.
390          * Because we are still holding AccessExclusiveLock on pg_shadow,
391          * we can be sure no other backend will try to write the flat
392          * file at the same time.
393          */
394         UpdatePgPwdFile();
395
396         /*
397          * Now we can clean up.
398          */
399         heap_close(pg_shadow_rel, AccessExclusiveLock);
400
401         if (IsTransactionBlock() && !inblock)
402                 EndTransactionBlock();
403 }
404
405
406 extern void
407 RemoveUser(char *user, CommandDest dest)
408 {
409         char       *pg_shadow;
410         Relation        pg_shadow_rel,
411                                 pg_rel;
412         TupleDesc       pg_dsc;
413         HeapScanDesc scan;
414         HeapTuple       tuple;
415         Datum           datum;
416         char            sql[SQL_LENGTH];
417         bool            n,
418                                 inblock;
419         int32           usesysid;
420         int                     ndbase = 0;
421         char      **dbase = NULL;
422
423         if (!(inblock = IsTransactionBlock()))
424                 BeginTransactionBlock();
425
426         /*
427          * Make sure the user attempting to create a user can delete from the
428          * pg_shadow relation.
429          */
430         pg_shadow = GetPgUserName();
431         if (pg_aclcheck(ShadowRelationName, pg_shadow, ACL_RD | ACL_WR) != ACLCHECK_OK)
432         {
433                 UserAbortTransactionBlock();
434                 elog(ERROR, "RemoveUser: user \"%s\" does not have SELECT and DELETE privilege for \"%s\"",
435                          pg_shadow, ShadowRelationName);
436         }
437
438         /*
439          * Scan the pg_shadow relation to find the usesysid of the user to be
440          * deleted.  Note we secure exclusive lock, because we need to protect
441          * our update of the flat password file.
442          */
443         pg_shadow_rel = heap_openr(ShadowRelationName, AccessExclusiveLock);
444         pg_dsc = RelationGetDescr(pg_shadow_rel);
445
446         tuple = SearchSysCacheTuple(SHADOWNAME,
447                                                                 PointerGetDatum(user),
448                                                                 0, 0, 0);
449         if (!HeapTupleIsValid(tuple))
450         {
451                 heap_close(pg_shadow_rel, AccessExclusiveLock);
452                 UserAbortTransactionBlock();
453                 elog(ERROR, "RemoveUser: user \"%s\" does not exist", user);
454         }
455
456         usesysid = (int32) heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_dsc, &n);
457
458         /*
459          * Perform a scan of the pg_database relation to find the databases
460          * owned by usesysid.  Then drop them.
461          */
462         pg_rel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
463         pg_dsc = RelationGetDescr(pg_rel);
464
465         scan = heap_beginscan(pg_rel, false, SnapshotNow, 0, NULL);
466         while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
467         {
468                 datum = heap_getattr(tuple, Anum_pg_database_datdba, pg_dsc, &n);
469
470                 if ((int) datum == usesysid)
471                 {
472                         datum = heap_getattr(tuple, Anum_pg_database_datname, pg_dsc, &n);
473                         if (memcmp((void *) datum, "template1", 9) != 0)
474                         {
475                                 dbase =
476                                         (char **) repalloc((void *) dbase, sizeof(char *) * (ndbase + 1));
477                                 dbase[ndbase] = (char *) palloc(NAMEDATALEN + 1);
478                                 memcpy((void *) dbase[ndbase], (void *) datum, NAMEDATALEN);
479                                 dbase[ndbase++][NAMEDATALEN] = '\0';
480                         }
481                 }
482         }
483         heap_endscan(scan);
484         heap_close(pg_rel, AccessExclusiveLock);
485
486         while (ndbase--)
487         {
488                 elog(NOTICE, "Dropping database %s", dbase[ndbase]);
489                 snprintf(sql, SQL_LENGTH, "DROP DATABASE %s", dbase[ndbase]);
490                 pfree((void *) dbase[ndbase]);
491                 pg_exec_query_dest(sql, dest, false);
492         }
493         if (dbase)
494                 pfree((void *) dbase);
495
496         /*
497          * Since pg_shadow is global over all databases, one of two things
498          * must be done to insure complete consistency.  First, pg_shadow
499          * could be made non-global. This would elminate the code above for
500          * deleting database and would require the addition of code to delete
501          * tables, views, etc owned by the user.
502          *
503          * The second option would be to create a means of deleting tables, view,
504          * etc. owned by the user from other databases.  pg_shadow is global
505          * and so this must be done at some point.
506          *
507          * Let us not forget that the user should be removed from the pg_groups
508          * also.
509          *
510          * Todd A. Brandys 11/18/1997
511          *
512          */
513
514         /*
515          * Remove the user from the pg_shadow table
516          */
517         snprintf(sql, SQL_LENGTH,
518                 "delete from %s where usename = '%s'", ShadowRelationName, user);
519         pg_exec_query_dest(sql, dest, false);
520
521         /*
522          * Write the updated pg_shadow data to the flat password file.
523          * Because we are still holding AccessExclusiveLock on pg_shadow,
524          * we can be sure no other backend will try to write the flat
525          * file at the same time.
526          */
527         UpdatePgPwdFile();
528
529         /*
530          * Now we can clean up.
531          */
532         heap_close(pg_shadow_rel, AccessExclusiveLock);
533
534         if (IsTransactionBlock() && !inblock)
535                 EndTransactionBlock();
536 }
537
538 /*
539  * CheckPgUserAclNotNull
540  *
541  * check to see if there is an ACL on pg_shadow
542  */
543 static void
544 CheckPgUserAclNotNull()
545 {
546         HeapTuple       htup;
547
548         htup = SearchSysCacheTuple(RELNAME,
549                                                            PointerGetDatum(ShadowRelationName),
550                                                            0, 0, 0);
551         if (!HeapTupleIsValid(htup))
552         {
553                 elog(ERROR, "IsPgUserAclNull: class \"%s\" not found",
554                          ShadowRelationName);
555         }
556
557         if (heap_attisnull(htup, Anum_pg_class_relacl))
558         {
559                 elog(NOTICE, "To use passwords, you have to revoke permissions on pg_shadow");
560                 elog(NOTICE, "so normal users can not read the passwords.");
561                 elog(ERROR, "Try 'REVOKE ALL ON pg_shadow FROM PUBLIC'");
562         }
563
564         return;
565 }
566
567
568 /*** GROUP THINGS ***/
569
570 void
571 CreateGroup(CreateGroupStmt *stmt, CommandDest dest)
572 {
573         Relation        pg_group_rel;
574         HeapScanDesc scan;
575         HeapTuple       tuple;
576     TupleDesc   pg_group_dsc;
577         bool            inblock;
578     bool        group_exists = false,
579                 sysid_exists = false;
580     int         max_id = -1;
581     Datum       new_record[Natts_pg_group];
582     char        new_record_nulls[Natts_pg_group];
583     List       *item, *newlist=NULL;
584     ArrayType  *userarray;
585
586
587         if (!(inblock = IsTransactionBlock()))
588                 BeginTransactionBlock();
589
590         /*
591          * Make sure the user can do this.
592          */
593         if (pg_aclcheck(GroupRelationName, GetPgUserName(), ACL_RD | ACL_AP) != ACLCHECK_OK)
594         {
595                 UserAbortTransactionBlock();
596                 elog(ERROR, "CreateGroup: Permission denied.");
597         }
598
599         pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
600         pg_group_dsc = RelationGetDescr(pg_group_rel);
601
602         scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
603         while (!group_exists && !sysid_exists && HeapTupleIsValid(tuple = heap_getnext(scan, false)))
604         {
605         Datum           datum;
606         bool        null;
607
608                 datum = heap_getattr(tuple, Anum_pg_group_groname, pg_group_dsc, &null);
609                 group_exists = datum && !null && (strcmp((char *) datum, stmt->name) == 0);
610
611                 datum = heap_getattr(tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
612         if (stmt->sysid >= 0) /* customized id wanted */
613             sysid_exists = datum && !null && ((int)datum == stmt->sysid);
614         else /* pick 1 + max */
615         {
616             if ((int) datum > max_id)
617                 max_id = (int) datum;
618         }
619         }
620         heap_endscan(scan);
621
622         if (group_exists || sysid_exists)
623         {
624                 heap_close(pg_group_rel, AccessExclusiveLock);
625                 UserAbortTransactionBlock();
626         if (group_exists)
627             elog(ERROR, "CreateGroup: Group name \"%s\" already exists.", stmt->name);
628         else
629             elog(ERROR, "CreateGroup: Group sysid %d is already assigned.", stmt->sysid);
630         }
631
632     /*
633      * Translate the given user names to ids
634      */
635
636     foreach(item, stmt->initUsers)
637     {
638         const char * groupuser = strVal(lfirst(item));
639         Value *v;
640
641         tuple = SearchSysCacheTuple(SHADOWNAME,
642                                     PointerGetDatum(groupuser),
643                                     0, 0, 0);
644         if (!HeapTupleIsValid(tuple))
645         {
646             heap_close(pg_group_rel, AccessExclusiveLock);
647             UserAbortTransactionBlock();
648             elog(ERROR, "CreateGroup: User \"%s\" does not exist.", groupuser);
649         }
650
651         v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
652         if (!member(v, newlist))
653             newlist = lcons(v, newlist);
654     }
655
656     /* build an array to insert */
657     if (newlist)
658     {
659         int i;
660
661         userarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
662         ARR_SIZE(userarray) = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
663         ARR_FLAGS(userarray) = 0x0;
664         ARR_NDIM(userarray) = 1; /* one dimensional array */
665         ARR_LBOUND(userarray)[0] = 1; /* axis starts at one */
666         ARR_DIMS(userarray)[0] = length(newlist); /* axis is this long */
667         /* fill the array */
668         i = 0;
669         foreach(item, newlist)
670         {
671             ((int*)ARR_DATA_PTR(userarray))[i++] = intVal(lfirst(item));
672         }
673     }
674     else
675         userarray = NULL;
676
677     /*
678      * Form a tuple to insert
679      */
680     if (stmt->sysid >=0)
681         max_id = stmt->sysid;
682     else 
683         max_id++;
684
685     new_record[Anum_pg_group_groname-1] = (Datum)(stmt->name);
686     new_record[Anum_pg_group_grosysid-1] = (Datum)(max_id);
687     new_record[Anum_pg_group_grolist-1] = (Datum)userarray;
688
689     new_record_nulls[Anum_pg_group_groname-1] = ' ';
690     new_record_nulls[Anum_pg_group_grosysid-1] = ' ';
691     new_record_nulls[Anum_pg_group_grolist-1] = userarray ? ' ' : 'n';
692
693     tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
694
695     /*
696      * Insert a new record in the pg_group_table
697      */
698     heap_insert(pg_group_rel, tuple);
699
700     /*
701      * Update indexes
702      */
703     if (RelationGetForm(pg_group_rel)->relhasindex) {
704         Relation idescs[Num_pg_group_indices];
705       
706         CatalogOpenIndices(Num_pg_group_indices, 
707                            Name_pg_group_indices, idescs);
708         CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel, 
709                            tuple);
710         CatalogCloseIndices(Num_pg_group_indices, idescs);
711     }
712
713         heap_close(pg_group_rel, NoLock);
714
715         if (IsTransactionBlock() && !inblock)
716                 EndTransactionBlock();
717 }
718
719
720
721 void
722 AlterGroup(AlterGroupStmt *stmt, CommandDest dest)
723 {
724         Relation        pg_group_rel;
725     TupleDesc   pg_group_dsc;
726         bool            inblock;
727     HeapTuple   group_tuple;
728
729         if (!(inblock = IsTransactionBlock()))
730                 BeginTransactionBlock();
731
732         /*
733          * Make sure the user can do this.
734          */
735         if (pg_aclcheck(GroupRelationName, GetPgUserName(), ACL_RD | ACL_WR) != ACLCHECK_OK)
736         {
737                 UserAbortTransactionBlock();
738                 elog(ERROR, "AlterGroup: Permission denied.");
739         }
740
741     pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
742     pg_group_dsc = RelationGetDescr(pg_group_rel);
743
744     /*
745      * Verify that group exists.
746      * If we find a tuple, will take that the rest of the way and make our
747      * modifications on it.
748      */
749     if (!HeapTupleIsValid(group_tuple = SearchSysCacheTupleCopy(GRONAME, PointerGetDatum(stmt->name), 0, 0, 0)))
750         {
751         heap_close(pg_group_rel, AccessExclusiveLock);
752                 UserAbortTransactionBlock();
753                 elog(ERROR, "AlterGroup: Group \"%s\" does not exist.", stmt->name);
754         }
755
756     /*
757      * Now decide what to do.
758      */
759     if (stmt->action == 0) /* change sysid */
760     {
761         bool          sysid_exists = false;
762         ScanKeyData   keys[2];
763         HeapTuple         tuple;
764         HeapScanDesc  scan;
765         Datum       new_record[Natts_pg_group];
766         char        new_record_nulls[Natts_pg_group];
767         bool null;
768
769         /*
770          * First check if the id is already assigned.
771          */
772         ScanKeyEntryInitialize(&keys[0], 0x0, Anum_pg_group_grosysid, F_INT4EQ,
773                                Int32GetDatum(stmt->sysid));
774         ScanKeyEntryInitialize(&keys[1], 0x0, Anum_pg_group_groname, F_NAMENE,
775                                PointerGetDatum(stmt->name));
776         scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 2, keys);
777
778         if (HeapTupleIsValid(heap_getnext(scan, false)))
779         {
780             heap_endscan(scan);
781             heap_close(pg_group_rel, AccessExclusiveLock);
782             UserAbortTransactionBlock();
783             elog(ERROR, "AlterGroup: Group sysid %d is already assigned.", stmt->sysid);
784         }
785         heap_endscan(scan);
786
787         /*
788          * Insert the new tuple with the updated sysid
789          */
790         new_record[Anum_pg_group_groname-1] = (Datum)(stmt->name);
791         new_record[Anum_pg_group_grosysid-1] = (Datum)(stmt->sysid);
792         new_record[Anum_pg_group_grolist-1] = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
793         new_record_nulls[Anum_pg_group_groname-1] = ' ';
794         new_record_nulls[Anum_pg_group_grosysid-1] = ' ';
795         new_record_nulls[Anum_pg_group_grolist-1] = null ? 'n' : ' ';
796
797         tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
798         heap_update(pg_group_rel, &group_tuple->t_self, tuple, NULL);
799
800         /* Update indexes */
801         if (RelationGetForm(pg_group_rel)->relhasindex) {
802             Relation idescs[Num_pg_group_indices];
803       
804             CatalogOpenIndices(Num_pg_group_indices, 
805                                Name_pg_group_indices, idescs);
806             CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel, 
807                                tuple);
808             CatalogCloseIndices(Num_pg_group_indices, idescs);
809         }
810     }
811
812     /*
813      * add users to group 
814      */
815     else if (stmt->action > 0)
816     {
817         Datum       new_record[Natts_pg_group];
818         char        new_record_nulls[Natts_pg_group] = { ' ', ' ', ' '};
819         ArrayType *newarray, *oldarray;
820         List * newlist = NULL, *item;
821         HeapTuple tuple;
822         bool null = false;
823         Datum datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
824         int i;
825         
826         oldarray = (ArrayType*)datum;
827         Assert(null || ARR_NDIM(oldarray) == 1);
828         /* first add the old array to the hitherto empty list */
829         if (!null)
830             for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
831             {
832                 int index, arrval;
833                 Value *v;
834                 bool valueNull;
835                 index = i;
836                 arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true/*by value*/,
837                                                  sizeof(int), 0, &valueNull));
838                 v = makeInteger(arrval);
839                 /* filter out duplicates */
840                 if (!member(v, newlist))
841                     newlist = lcons(v, newlist);
842             }
843
844         /* 
845          * now convert the to be added usernames to sysids and add them
846          * to the list
847          */
848         foreach(item, stmt->listUsers)
849         {
850             Value *v;
851             /* Get the uid of the proposed user to add. */
852             tuple = SearchSysCacheTuple(SHADOWNAME,
853                                         PointerGetDatum(strVal(lfirst(item))),
854                                         0, 0, 0);
855             if (!HeapTupleIsValid(tuple))
856             {
857                 heap_close(pg_group_rel, AccessExclusiveLock);
858                 UserAbortTransactionBlock();
859                 elog(ERROR, "AlterGroup: User \"%s\" does not exist.", strVal(lfirst(item)));
860             }
861             
862             v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
863             if (!member(v, newlist))
864                 newlist = lcons(v, newlist);
865             else
866                 elog(NOTICE, "AlterGroup: User \"%s\" is already in group \"%s\".", strVal(lfirst(item)), stmt->name);
867         }
868              
869         newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
870         ARR_SIZE(newarray) = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
871         ARR_FLAGS(newarray) = 0x0;
872         ARR_NDIM(newarray) = 1; /* one dimensional array */
873         ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */
874         ARR_DIMS(newarray)[0] = length(newlist); /* axis is this long */
875         /* fill the array */
876         i = 0;
877         foreach(item, newlist)
878         {
879             ((int*)ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));
880         }
881         
882         /*
883          * Form a tuple with the new array and write it back.
884          */
885         new_record[Anum_pg_group_groname-1] = (Datum)(stmt->name);
886         new_record[Anum_pg_group_grosysid-1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
887         new_record[Anum_pg_group_grolist-1] = PointerGetDatum(newarray);
888
889         tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
890         heap_update(pg_group_rel, &group_tuple->t_self, tuple, NULL);
891
892         /* Update indexes */
893         if (RelationGetForm(pg_group_rel)->relhasindex) {
894             Relation idescs[Num_pg_group_indices];
895       
896             CatalogOpenIndices(Num_pg_group_indices, 
897                                Name_pg_group_indices, idescs);
898             CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel, 
899                                tuple);
900             CatalogCloseIndices(Num_pg_group_indices, idescs);
901         }
902     } /* endif alter group add user */
903
904     /*
905      * drop users from group
906      */
907     else if (stmt->action < 0)
908     {
909         Datum         datum;
910         bool          null;
911         
912         datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
913         if (null)
914             elog(NOTICE, "AlterGroup: Group \"%s\"'s membership is NULL.", stmt->name);
915         else
916         {
917             HeapTuple     tuple;
918             Datum       new_record[Natts_pg_group];
919             char        new_record_nulls[Natts_pg_group] = { ' ', ' ', ' '};
920             ArrayType    *oldarray, *newarray;
921             List * newlist = NULL, *item;
922             int i;
923
924             oldarray = (ArrayType*)datum;
925             Assert(ARR_NDIM(oldarray) == 1);
926             /* first add the old array to the hitherto empty list */
927             for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
928             {
929                 int index, arrval;
930                 Value *v;
931                 bool valueNull;
932                 index = i;
933                 arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true/*by value*/,
934                                                  sizeof(int), 0, &valueNull));
935                 v = makeInteger(arrval);
936                 /* filter out duplicates */
937                 if (!member(v, newlist))
938                     newlist = lcons(v, newlist);
939             }
940
941             /* 
942              * now convert the to be dropped usernames to sysids and remove
943              * them from the list
944              */
945             foreach(item, stmt->listUsers)
946             {
947                 Value *v;
948                 /* Get the uid of the proposed user to drop. */
949                 tuple = SearchSysCacheTuple(SHADOWNAME,
950                                             PointerGetDatum(strVal(lfirst(item))),
951                                             0, 0, 0);
952                 if (!HeapTupleIsValid(tuple))
953                 {
954                     heap_close(pg_group_rel, AccessExclusiveLock);
955                     UserAbortTransactionBlock();
956                     elog(ERROR, "AlterGroup: User \"%s\" does not exist.", strVal(lfirst(item)));
957                 }
958             
959                 v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
960                 if (member(v, newlist))
961                     newlist = LispRemove(v, newlist);
962                 else
963                     elog(NOTICE, "AlterGroup: User \"%s\" is not in group \"%s\".", strVal(lfirst(item)), stmt->name);
964             }
965
966             newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
967             ARR_SIZE(newarray) = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
968             ARR_FLAGS(newarray) = 0x0;
969             ARR_NDIM(newarray) = 1; /* one dimensional array */
970             ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */
971             ARR_DIMS(newarray)[0] = length(newlist); /* axis is this long */
972             /* fill the array */
973             i = 0;
974             foreach(item, newlist)
975             {
976                 ((int*)ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));
977             }
978         
979             /*
980              * Insert the new tuple with the updated user list
981              */
982             new_record[Anum_pg_group_groname-1] = (Datum)(stmt->name);
983             new_record[Anum_pg_group_grosysid-1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
984             new_record[Anum_pg_group_grolist-1] = PointerGetDatum(newarray);
985
986             tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
987             heap_update(pg_group_rel, &group_tuple->t_self, tuple, NULL);
988
989             /* Update indexes */
990             if (RelationGetForm(pg_group_rel)->relhasindex) {
991                 Relation idescs[Num_pg_group_indices];
992       
993                 CatalogOpenIndices(Num_pg_group_indices, 
994                                    Name_pg_group_indices, idescs);
995                 CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel, 
996                                    tuple);
997                 CatalogCloseIndices(Num_pg_group_indices, idescs);
998             }
999
1000         } /* endif group not null */
1001     } /* endif alter group drop user */
1002
1003     heap_close(pg_group_rel, NoLock);
1004
1005     pfree(group_tuple);
1006
1007         if (IsTransactionBlock() && !inblock)
1008                 EndTransactionBlock();
1009 }
1010
1011
1012
1013 void
1014 DropGroup(DropGroupStmt *stmt, CommandDest dest)
1015 {
1016         Relation        pg_group_rel;
1017         HeapScanDesc scan;
1018         HeapTuple       tuple;
1019     TupleDesc   pg_group_dsc;
1020         bool            inblock;
1021     bool        gro_exists = false;
1022
1023         if (!(inblock = IsTransactionBlock()))
1024                 BeginTransactionBlock();
1025
1026         /*
1027          * Make sure the user can do this.
1028          */
1029         if (pg_aclcheck(GroupRelationName, GetPgUserName(), ACL_RD | ACL_WR) != ACLCHECK_OK)
1030         {
1031                 UserAbortTransactionBlock();
1032                 elog(ERROR, "DropGroup: Permission denied.");
1033         }
1034
1035     /*
1036      * Scan the pg_group table and delete all matching users.
1037      */
1038         pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
1039         pg_group_dsc = RelationGetDescr(pg_group_rel);
1040         scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
1041
1042         while (HeapTupleIsValid(tuple = heap_getnext(scan, false)))
1043         {
1044         Datum datum;
1045         bool null;
1046
1047         datum = heap_getattr(tuple, Anum_pg_group_groname, pg_group_dsc, &null);
1048         if (datum && !null && strcmp((char*)datum, stmt->name)==0)
1049         {
1050             gro_exists = true;
1051             heap_delete(pg_group_rel, &tuple->t_self, NULL);
1052         }
1053
1054         }
1055
1056         heap_endscan(scan);
1057
1058     /*
1059      * Did we find any?
1060      */
1061     if (!gro_exists)
1062     {
1063         heap_close(pg_group_rel, AccessExclusiveLock);
1064                 UserAbortTransactionBlock();
1065                 elog(ERROR, "DropGroup: Group \"%s\" does not exist.", stmt->name);
1066     }
1067
1068         heap_close(pg_group_rel, NoLock);
1069
1070         if (IsTransactionBlock() && !inblock)
1071                 EndTransactionBlock();
1072 }
1073