]> granicus.if.org Git - postgresql/blob - src/backend/commands/user.c
has_table_privilege functions from Joe Conway (with some kibitzing from
[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.77 2001/06/14 01:09:22 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 <unistd.h>
19
20 #include "access/heapam.h"
21 #include "catalog/catname.h"
22 #include "catalog/pg_database.h"
23 #include "catalog/pg_shadow.h"
24 #include "catalog/pg_group.h"
25 #include "catalog/indexing.h"
26 #include "commands/user.h"
27 #include "libpq/crypt.h"
28 #include "miscadmin.h"
29 #include "utils/array.h"
30 #include "utils/builtins.h"
31 #include "utils/fmgroids.h"
32 #include "utils/lsyscache.h"
33 #include "utils/syscache.h"
34
35
36 static void CheckPgUserAclNotNull(void);
37
38
39 /*---------------------------------------------------------------------
40  * write_password_file / update_pg_pwd
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  * This function set is both a trigger function for direct updates to pg_shadow
46  * as well as being called directly from create/alter/drop user.
47  *---------------------------------------------------------------------
48  */
49 static void
50 write_password_file(Relation rel)
51 {
52         char       *filename,
53                            *tempname;
54         int                     bufsize;
55         FILE       *fp;
56         int                     flagfd;
57         mode_t          oumask;
58         HeapScanDesc scan;
59         HeapTuple       tuple;
60         TupleDesc       dsc = RelationGetDescr(rel);
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
71         snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
72         oumask = umask((mode_t) 077);
73         fp = AllocateFile(tempname, "w");
74         umask(oumask);
75         if (fp == NULL)
76                 elog(ERROR, "write_password_file: unable to write %s: %m", tempname);
77
78         /* read table */
79         scan = heap_beginscan(rel, false, SnapshotSelf, 0, NULL);
80         while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
81         {
82                 Datum           datum_n,
83                                         datum_p,
84                                         datum_v;
85                 bool            null_n,
86                                         null_p,
87                                         null_v;
88
89                 datum_n = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &null_n);
90                 if (null_n)
91                         continue;                       /* don't allow empty users */
92                 datum_p = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &null_p);
93
94                 /*
95                  * It could be argued that people having a null password shouldn't
96                  * be allowed to connect, because they need to have a password set
97                  * up first. If you think assuming an empty password in that case
98                  * is better, erase the following line.
99                  */
100                 if (null_p)
101                         continue;
102                 datum_v = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &null_v);
103
104                 /*
105                  * These fake entries are not really necessary. To remove them,
106                  * the parser in backend/libpq/crypt.c would need to be adjusted.
107                  * Initdb might also need adjustments.
108                  */
109                 fprintf(fp,
110                                 "%s"
111                                 CRYPT_PWD_FILE_SEPSTR
112                                 "0"
113                                 CRYPT_PWD_FILE_SEPSTR
114                                 "x"
115                                 CRYPT_PWD_FILE_SEPSTR
116                                 "x"
117                                 CRYPT_PWD_FILE_SEPSTR
118                                 "x"
119                                 CRYPT_PWD_FILE_SEPSTR
120                                 "x"
121                                 CRYPT_PWD_FILE_SEPSTR
122                                 "%s"
123                                 CRYPT_PWD_FILE_SEPSTR
124                                 "%s\n",
125                                 DatumGetCString(DirectFunctionCall1(nameout, datum_n)),
126                                 null_p ? "" :
127                                 DatumGetCString(DirectFunctionCall1(textout, datum_p)),
128                                 null_v ? "\\N" :
129                                 DatumGetCString(DirectFunctionCall1(nabstimeout, datum_v))
130                         );
131         }
132         heap_endscan(scan);
133
134         fflush(fp);
135         if (ferror(fp))
136                 elog(ERROR, "%s: %m", tempname);
137         FreeFile(fp);
138
139         /*
140          * And rename the temp file to its final name, deleting the old
141          * pg_pwd.
142          */
143         if (rename(tempname, filename))
144                 elog(ERROR, "rename %s to %s: %m", tempname, filename);
145
146         pfree((void *) tempname);
147         pfree((void *) filename);
148
149         /*
150          * Create a flag file the postmaster will detect the next time it
151          * tries to authenticate a user.  The postmaster will know to reload
152          * the pg_pwd file contents.  Note: we used to elog(ERROR) if the file
153          * creation failed, but it's a little silly to abort the transaction
154          * at this point, so let's just make it a NOTICE.
155          */
156         filename = crypt_getpwdreloadfilename();
157         flagfd = BasicOpenFile(filename, O_WRONLY | O_CREAT, 0600);
158         if (flagfd < 0)
159                 elog(NOTICE, "write_password_file: unable to write %s: %m", filename);
160         else
161                 close(flagfd);
162         pfree((void *) filename);
163 }
164
165
166
167 /* This is the wrapper for triggers. */
168 Datum
169 update_pg_pwd(PG_FUNCTION_ARGS)
170 {
171         /*
172          * ExclusiveLock ensures no one modifies pg_shadow while we read it,
173          * and that only one backend rewrites the flat file at a time.  It's
174          * OK to allow normal reads of pg_shadow in parallel, however.
175          */
176         Relation        rel = heap_openr(ShadowRelationName, ExclusiveLock);
177
178         write_password_file(rel);
179         /* OK to release lock, since we did not modify the relation */
180         heap_close(rel, ExclusiveLock);
181         return PointerGetDatum(NULL);
182 }
183
184
185
186 /*
187  * CREATE USER
188  */
189 void
190 CreateUser(CreateUserStmt *stmt)
191 {
192         Relation        pg_shadow_rel;
193         TupleDesc       pg_shadow_dsc;
194         HeapScanDesc scan;
195         HeapTuple       tuple;
196         Datum           new_record[Natts_pg_shadow];
197         char            new_record_nulls[Natts_pg_shadow];
198         bool            user_exists = false,
199                                 sysid_exists = false,
200                                 havesysid;
201         int                     max_id = -1;
202         List       *item;
203
204         havesysid = stmt->sysid > 0;
205
206         /* Check some permissions first */
207         if (stmt->password)
208                 CheckPgUserAclNotNull();
209
210         if (!superuser())
211                 elog(ERROR, "CREATE USER: permission denied");
212
213         /*
214          * Scan the pg_shadow relation to be certain the user or id doesn't
215          * already exist.  Note we secure exclusive lock, because we also need
216          * to be sure of what the next usesysid should be, and we need to
217          * protect our update of the flat password file.
218          */
219         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
220         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
221
222         scan = heap_beginscan(pg_shadow_rel, false, SnapshotNow, 0, NULL);
223         while (!user_exists && !sysid_exists &&
224                    HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
225         {
226                 Datum           datum;
227                 bool            null;
228
229                 datum = heap_getattr(tuple, Anum_pg_shadow_usename,
230                                                          pg_shadow_dsc, &null);
231                 Assert(!null);
232                 user_exists = (strcmp((char *) DatumGetName(datum), stmt->user) == 0);
233
234                 datum = heap_getattr(tuple, Anum_pg_shadow_usesysid,
235                                                          pg_shadow_dsc, &null);
236                 Assert(!null);
237                 if (havesysid)                  /* customized id wanted */
238                         sysid_exists = (DatumGetInt32(datum) == stmt->sysid);
239                 else
240                 {
241                         /* pick 1 + max */
242                         if (DatumGetInt32(datum) > max_id)
243                                 max_id = DatumGetInt32(datum);
244                 }
245         }
246         heap_endscan(scan);
247
248         if (user_exists)
249                 elog(ERROR, "CREATE USER: user name \"%s\" already exists",
250                          stmt->user);
251         if (sysid_exists)
252                 elog(ERROR, "CREATE USER: sysid %d is already assigned",
253                          stmt->sysid);
254
255         /*
256          * Build a tuple to insert
257          */
258         new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
259                                                                                         CStringGetDatum(stmt->user));
260         new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(havesysid ? stmt->sysid : max_id + 1);
261
262         AssertState(BoolIsValid(stmt->createdb));
263         new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(stmt->createdb);
264         new_record[Anum_pg_shadow_usetrace - 1] = BoolGetDatum(false);
265         AssertState(BoolIsValid(stmt->createuser));
266         new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(stmt->createuser);
267         /* superuser gets catupd right by default */
268         new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(stmt->createuser);
269
270         if (stmt->password)
271                 new_record[Anum_pg_shadow_passwd - 1] =
272                         DirectFunctionCall1(textin, CStringGetDatum(stmt->password));
273         if (stmt->validUntil)
274                 new_record[Anum_pg_shadow_valuntil - 1] =
275                         DirectFunctionCall1(nabstimein, CStringGetDatum(stmt->validUntil));
276
277         new_record_nulls[Anum_pg_shadow_usename - 1] = ' ';
278         new_record_nulls[Anum_pg_shadow_usesysid - 1] = ' ';
279
280         new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = ' ';
281         new_record_nulls[Anum_pg_shadow_usetrace - 1] = ' ';
282         new_record_nulls[Anum_pg_shadow_usesuper - 1] = ' ';
283         new_record_nulls[Anum_pg_shadow_usecatupd - 1] = ' ';
284
285         new_record_nulls[Anum_pg_shadow_passwd - 1] = stmt->password ? ' ' : 'n';
286         new_record_nulls[Anum_pg_shadow_valuntil - 1] = stmt->validUntil ? ' ' : 'n';
287
288         tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
289
290         /*
291          * Insert new record in the pg_shadow table
292          */
293         heap_insert(pg_shadow_rel, tuple);
294
295         /*
296          * Update indexes
297          */
298         if (RelationGetForm(pg_shadow_rel)->relhasindex)
299         {
300                 Relation        idescs[Num_pg_shadow_indices];
301
302                 CatalogOpenIndices(Num_pg_shadow_indices,
303                                                    Name_pg_shadow_indices, idescs);
304                 CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
305                                                    tuple);
306                 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
307         }
308
309         /*
310          * Add the user to the groups specified. We'll just call the below
311          * AlterGroup for this.
312          */
313         foreach(item, stmt->groupElts)
314         {
315                 AlterGroupStmt ags;
316
317                 ags.name = strVal(lfirst(item));                /* the group name to add
318                                                                                                  * this in */
319                 ags.action = +1;
320                 ags.listUsers = makeList1(makeInteger(havesysid ?
321                                                                                           stmt->sysid : max_id + 1));
322                 AlterGroup(&ags, "CREATE USER");
323         }
324
325         /*
326          * Write the updated pg_shadow data to the flat password file.
327          */
328         write_password_file(pg_shadow_rel);
329
330         /*
331          * Now we can clean up; but keep lock until commit.
332          */
333         heap_close(pg_shadow_rel, NoLock);
334 }
335
336
337
338 /*
339  * ALTER USER
340  */
341 extern void
342 AlterUser(AlterUserStmt *stmt)
343 {
344         Datum           new_record[Natts_pg_shadow];
345         char            new_record_nulls[Natts_pg_shadow];
346         Relation        pg_shadow_rel;
347         TupleDesc       pg_shadow_dsc;
348         HeapTuple       tuple,
349                                 new_tuple;
350         bool            null;
351
352         if (stmt->password)
353                 CheckPgUserAclNotNull();
354
355         /* must be superuser or just want to change your own password */
356         if (!superuser() &&
357                 !(stmt->createdb == 0 &&
358                   stmt->createuser == 0 &&
359                   !stmt->validUntil &&
360                   stmt->password &&
361                   strcmp(GetUserName(GetUserId()), stmt->user) == 0))
362                 elog(ERROR, "ALTER USER: permission denied");
363
364         /* changes to the flat password file cannot be rolled back */
365         if (IsTransactionBlock() && stmt->password)
366                 elog(NOTICE, "ALTER USER: password changes cannot be rolled back");
367
368         /*
369          * Scan the pg_shadow relation to be certain the user exists. Note we
370          * secure exclusive lock to protect our update of the flat password
371          * file.
372          */
373         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
374         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
375
376         tuple = SearchSysCache(SHADOWNAME,
377                                                    PointerGetDatum(stmt->user),
378                                                    0, 0, 0);
379         if (!HeapTupleIsValid(tuple))
380                 elog(ERROR, "ALTER USER: user \"%s\" does not exist", stmt->user);
381
382         /*
383          * Build a tuple to update, perusing the information just obtained
384          */
385         new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
386                                                                                         CStringGetDatum(stmt->user));
387         new_record_nulls[Anum_pg_shadow_usename - 1] = ' ';
388
389         /* sysid - leave as is */
390         new_record[Anum_pg_shadow_usesysid - 1] = heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null);
391         new_record_nulls[Anum_pg_shadow_usesysid - 1] = null ? 'n' : ' ';
392
393         /* createdb */
394         if (stmt->createdb == 0)
395         {
396                 /* don't change */
397                 new_record[Anum_pg_shadow_usecreatedb - 1] = heap_getattr(tuple, Anum_pg_shadow_usecreatedb, pg_shadow_dsc, &null);
398                 new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = null ? 'n' : ' ';
399         }
400         else
401         {
402                 new_record[Anum_pg_shadow_usecreatedb - 1] = (Datum) (stmt->createdb > 0 ? true : false);
403                 new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = ' ';
404         }
405
406         /* trace - leave as is */
407         new_record[Anum_pg_shadow_usetrace - 1] = heap_getattr(tuple, Anum_pg_shadow_usetrace, pg_shadow_dsc, &null);
408         new_record_nulls[Anum_pg_shadow_usetrace - 1] = null ? 'n' : ' ';
409
410         /* createuser (superuser) */
411         if (stmt->createuser == 0)
412         {
413                 /* don't change */
414                 new_record[Anum_pg_shadow_usesuper - 1] = heap_getattr(tuple, Anum_pg_shadow_usesuper, pg_shadow_dsc, &null);
415                 new_record_nulls[Anum_pg_shadow_usesuper - 1] = null ? 'n' : ' ';
416         }
417         else
418         {
419                 new_record[Anum_pg_shadow_usesuper - 1] = (Datum) (stmt->createuser > 0 ? true : false);
420                 new_record_nulls[Anum_pg_shadow_usesuper - 1] = ' ';
421         }
422
423         /* catupd - set to false if someone's superuser priv is being yanked */
424         if (stmt->createuser < 0)
425         {
426                 new_record[Anum_pg_shadow_usecatupd - 1] = (Datum) (false);
427                 new_record_nulls[Anum_pg_shadow_usecatupd - 1] = ' ';
428         }
429         else
430         {
431                 /* leave alone */
432                 new_record[Anum_pg_shadow_usecatupd - 1] = heap_getattr(tuple, Anum_pg_shadow_usecatupd, pg_shadow_dsc, &null);
433                 new_record_nulls[Anum_pg_shadow_usecatupd - 1] = null ? 'n' : ' ';
434         }
435
436         /* password */
437         if (stmt->password)
438         {
439                 new_record[Anum_pg_shadow_passwd - 1] =
440                         DirectFunctionCall1(textin, CStringGetDatum(stmt->password));
441                 new_record_nulls[Anum_pg_shadow_passwd - 1] = ' ';
442         }
443         else
444         {
445                 /* leave as is */
446                 new_record[Anum_pg_shadow_passwd - 1] =
447                         heap_getattr(tuple, Anum_pg_shadow_passwd, pg_shadow_dsc, &null);
448                 new_record_nulls[Anum_pg_shadow_passwd - 1] = null ? 'n' : ' ';
449         }
450
451         /* valid until */
452         if (stmt->validUntil)
453         {
454                 new_record[Anum_pg_shadow_valuntil - 1] =
455                         DirectFunctionCall1(nabstimein, CStringGetDatum(stmt->validUntil));
456                 new_record_nulls[Anum_pg_shadow_valuntil - 1] = ' ';
457         }
458         else
459         {
460                 /* leave as is */
461                 new_record[Anum_pg_shadow_valuntil - 1] =
462                         heap_getattr(tuple, Anum_pg_shadow_valuntil, pg_shadow_dsc, &null);
463                 new_record_nulls[Anum_pg_shadow_valuntil - 1] = null ? 'n' : ' ';
464         }
465
466         new_tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
467         simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
468
469         /* Update indexes */
470         if (RelationGetForm(pg_shadow_rel)->relhasindex)
471         {
472                 Relation        idescs[Num_pg_shadow_indices];
473
474                 CatalogOpenIndices(Num_pg_shadow_indices,
475                                                    Name_pg_shadow_indices, idescs);
476                 CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
477                                                    new_tuple);
478                 CatalogCloseIndices(Num_pg_shadow_indices, idescs);
479         }
480
481         ReleaseSysCache(tuple);
482         heap_freetuple(new_tuple);
483
484         /*
485          * Write the updated pg_shadow data to the flat password file.
486          */
487         write_password_file(pg_shadow_rel);
488
489         /*
490          * Now we can clean up.
491          */
492         heap_close(pg_shadow_rel, NoLock);
493 }
494
495
496
497 /*
498  * DROP USER
499  */
500 void
501 DropUser(DropUserStmt *stmt)
502 {
503         Relation        pg_shadow_rel;
504         TupleDesc       pg_shadow_dsc;
505         List       *item;
506
507         if (!superuser())
508                 elog(ERROR, "DROP USER: permission denied");
509
510         if (IsTransactionBlock())
511                 elog(NOTICE, "DROP USER cannot be rolled back completely");
512
513         /*
514          * Scan the pg_shadow relation to find the usesysid of the user to be
515          * deleted.  Note we secure exclusive lock, because we need to protect
516          * our update of the flat password file.
517          */
518         pg_shadow_rel = heap_openr(ShadowRelationName, ExclusiveLock);
519         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
520
521         foreach(item, stmt->users)
522         {
523                 HeapTuple       tuple,
524                                         tmp_tuple;
525                 Relation        pg_rel;
526                 TupleDesc       pg_dsc;
527                 ScanKeyData scankey;
528                 HeapScanDesc scan;
529                 Datum           datum;
530                 bool            null;
531                 int32           usesysid;
532                 const char *user = strVal(lfirst(item));
533
534                 tuple = SearchSysCache(SHADOWNAME,
535                                                            PointerGetDatum(user),
536                                                            0, 0, 0);
537                 if (!HeapTupleIsValid(tuple))
538                         elog(ERROR, "DROP USER: user \"%s\" does not exist%s", user,
539                                  (length(stmt->users) > 1) ? " (no users removed)" : "");
540
541                 usesysid = DatumGetInt32(heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null));
542
543                 /*
544                  * Check if user still owns a database. If so, error out.
545                  *
546                  * (It used to be that this function would drop the database
547                  * automatically. This is not only very dangerous for people that
548                  * don't read the manual, it doesn't seem to be the behaviour one
549                  * would expect either.) -- petere 2000/01/14)
550                  */
551                 pg_rel = heap_openr(DatabaseRelationName, AccessShareLock);
552                 pg_dsc = RelationGetDescr(pg_rel);
553
554                 ScanKeyEntryInitialize(&scankey, 0x0,
555                                                            Anum_pg_database_datdba, F_INT4EQ,
556                                                            Int32GetDatum(usesysid));
557
558                 scan = heap_beginscan(pg_rel, false, SnapshotNow, 1, &scankey);
559
560                 if (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
561                 {
562                         char   *dbname;
563
564                         datum = heap_getattr(tmp_tuple, Anum_pg_database_datname,
565                                                                  pg_dsc, &null);
566                         Assert(!null);
567                         dbname = DatumGetCString(DirectFunctionCall1(nameout, datum));
568                         elog(ERROR, "DROP USER: user \"%s\" owns database \"%s\", cannot be removed%s",
569                                  user, dbname,
570                                  (length(stmt->users) > 1) ? " (no users removed)" : "");
571                 }
572
573                 heap_endscan(scan);
574                 heap_close(pg_rel, AccessShareLock);
575
576                 /*
577                  * Somehow we'd have to check for tables, views, etc. owned by the
578                  * user as well, but those could be spread out over all sorts of
579                  * databases which we don't have access to (easily).
580                  */
581
582                 /*
583                  * Remove the user from the pg_shadow table
584                  */
585                 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
586
587                 ReleaseSysCache(tuple);
588
589                 /*
590                  * Remove user from groups
591                  *
592                  * try calling alter group drop user for every group
593                  */
594                 pg_rel = heap_openr(GroupRelationName, ExclusiveLock);
595                 pg_dsc = RelationGetDescr(pg_rel);
596                 scan = heap_beginscan(pg_rel, false, SnapshotNow, 0, NULL);
597                 while (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
598                 {
599                         AlterGroupStmt ags;
600
601                         /* the group name from which to try to drop the user: */
602                         datum = heap_getattr(tmp_tuple, Anum_pg_group_groname, pg_dsc, &null);
603                         ags.name = DatumGetCString(DirectFunctionCall1(nameout, datum));
604                         ags.action = -1;
605                         ags.listUsers = makeList1(makeInteger(usesysid));
606                         AlterGroup(&ags, "DROP USER");
607                 }
608                 heap_endscan(scan);
609                 heap_close(pg_rel, ExclusiveLock);
610
611                 /*
612                  * Advance command counter so that later iterations of this loop
613                  * will see the changes already made.  This is essential if, for
614                  * example, we are trying to drop two users who are members of the
615                  * same group --- the AlterGroup for the second user had better
616                  * see the tuple updated from the first one.
617                  */
618                 CommandCounterIncrement();
619         }
620
621         /*
622          * Write the updated pg_shadow data to the flat password file.
623          */
624         write_password_file(pg_shadow_rel);
625
626         /*
627          * Now we can clean up.
628          */
629         heap_close(pg_shadow_rel, NoLock);
630 }
631
632
633
634 /*
635  * CheckPgUserAclNotNull
636  *
637  * check to see if there is an ACL on pg_shadow
638  */
639 static void
640 CheckPgUserAclNotNull()
641 {
642         HeapTuple       htup;
643
644         htup = SearchSysCache(RELNAME,
645                                                   PointerGetDatum(ShadowRelationName),
646                                                   0, 0, 0);
647         if (!HeapTupleIsValid(htup))
648                 elog(ERROR, "CheckPgUserAclNotNull: \"%s\" not found",
649                          ShadowRelationName);
650
651         if (heap_attisnull(htup, Anum_pg_class_relacl))
652                 elog(ERROR,
653                          "To use passwords, you have to revoke permissions on %s "
654                          "so normal users cannot read the passwords. "
655                          "Try 'REVOKE ALL ON \"%s\" FROM PUBLIC'.",
656                          ShadowRelationName, ShadowRelationName);
657
658         ReleaseSysCache(htup);
659 }
660
661
662
663 /*
664  * CREATE GROUP
665  */
666 void
667 CreateGroup(CreateGroupStmt *stmt)
668 {
669         Relation        pg_group_rel;
670         HeapScanDesc scan;
671         HeapTuple       tuple;
672         TupleDesc       pg_group_dsc;
673         bool            group_exists = false,
674                                 sysid_exists = false;
675         int                     max_id = 0;
676         Datum           new_record[Natts_pg_group];
677         char            new_record_nulls[Natts_pg_group];
678         List       *item,
679                            *newlist = NULL;
680         ArrayType  *userarray;
681
682         /*
683          * Make sure the user can do this.
684          */
685         if (!superuser())
686                 elog(ERROR, "CREATE GROUP: permission denied");
687
688         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
689         pg_group_dsc = RelationGetDescr(pg_group_rel);
690
691         scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
692         while (!group_exists && !sysid_exists &&
693                    HeapTupleIsValid(tuple = heap_getnext(scan, false)))
694         {
695                 Datum           datum;
696                 bool            null;
697
698                 datum = heap_getattr(tuple, Anum_pg_group_groname,
699                                                          pg_group_dsc, &null);
700                 Assert(!null);
701                 group_exists = (strcmp((char *) DatumGetName(datum), stmt->name) == 0);
702
703                 datum = heap_getattr(tuple, Anum_pg_group_grosysid,
704                                                          pg_group_dsc, &null);
705                 Assert(!null);
706                 if (stmt->sysid >= 0)   /* customized id wanted */
707                         sysid_exists = (DatumGetInt32(datum) == stmt->sysid);
708                 else
709                 {
710                         /* pick 1 + max */
711                         if (DatumGetInt32(datum) > max_id)
712                                 max_id = DatumGetInt32(datum);
713                 }
714         }
715         heap_endscan(scan);
716
717         if (group_exists)
718                 elog(ERROR, "CREATE GROUP: group name \"%s\" already exists",
719                          stmt->name);
720         if (sysid_exists)
721                 elog(ERROR, "CREATE GROUP: group sysid %d is already assigned",
722                          stmt->sysid);
723
724         /*
725          * Translate the given user names to ids
726          */
727         foreach(item, stmt->initUsers)
728         {
729                 const char *groupuser = strVal(lfirst(item));
730                 Value      *v;
731
732                 v = makeInteger(get_usesysid(groupuser));
733                 if (!member(v, newlist))
734                         newlist = lcons(v, newlist);
735         }
736
737         /* build an array to insert */
738         if (newlist)
739         {
740                 int                     i;
741
742                 userarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
743                 userarray->size = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
744                 userarray->flags = 0;
745                 ARR_NDIM(userarray) = 1;/* one dimensional array */
746                 ARR_LBOUND(userarray)[0] = 1;   /* axis starts at one */
747                 ARR_DIMS(userarray)[0] = length(newlist);               /* axis is this long */
748                 /* fill the array */
749                 i = 0;
750                 foreach(item, newlist)
751                         ((int *) ARR_DATA_PTR(userarray))[i++] = intVal(lfirst(item));
752         }
753         else
754                 userarray = NULL;
755
756         /*
757          * Form a tuple to insert
758          */
759         if (stmt->sysid >= 0)
760                 max_id = stmt->sysid;
761         else
762                 max_id++;
763
764         new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
765         new_record[Anum_pg_group_grosysid - 1] = (Datum) (max_id);
766         new_record[Anum_pg_group_grolist - 1] = (Datum) userarray;
767
768         new_record_nulls[Anum_pg_group_groname - 1] = ' ';
769         new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
770         new_record_nulls[Anum_pg_group_grolist - 1] = userarray ? ' ' : 'n';
771
772         tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
773
774         /*
775          * Insert a new record in the pg_group_table
776          */
777         heap_insert(pg_group_rel, tuple);
778
779         /*
780          * Update indexes
781          */
782         if (RelationGetForm(pg_group_rel)->relhasindex)
783         {
784                 Relation        idescs[Num_pg_group_indices];
785
786                 CatalogOpenIndices(Num_pg_group_indices,
787                                                    Name_pg_group_indices, idescs);
788                 CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
789                                                    tuple);
790                 CatalogCloseIndices(Num_pg_group_indices, idescs);
791         }
792
793         heap_close(pg_group_rel, NoLock);
794 }
795
796
797
798 /*
799  * ALTER GROUP
800  */
801 void
802 AlterGroup(AlterGroupStmt *stmt, const char *tag)
803 {
804         Relation        pg_group_rel;
805         TupleDesc       pg_group_dsc;
806         HeapTuple       group_tuple;
807
808         /*
809          * Make sure the user can do this.
810          */
811         if (!superuser())
812                 elog(ERROR, "%s: permission denied", tag);
813
814         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
815         pg_group_dsc = RelationGetDescr(pg_group_rel);
816
817         /*
818          * Fetch existing tuple for group.
819          */
820         group_tuple = SearchSysCache(GRONAME,
821                                                                  PointerGetDatum(stmt->name),
822                                                                  0, 0, 0);
823         if (!HeapTupleIsValid(group_tuple))
824                 elog(ERROR, "%s: group \"%s\" does not exist", tag, stmt->name);
825
826         /*
827          * Now decide what to do.
828          */
829         AssertState(stmt->action == +1 || stmt->action == -1);
830
831         if (stmt->action == +1)         /* add users, might also be invoked by
832                                                                  * create user */
833         {
834                 Datum           new_record[Natts_pg_group];
835                 char            new_record_nulls[Natts_pg_group] = {' ', ' ', ' '};
836                 ArrayType  *newarray,
837                                    *oldarray;
838                 List       *newlist = NULL,
839                                    *item;
840                 HeapTuple       tuple;
841                 bool            null = false;
842                 Datum           datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
843                 int                     i;
844
845                 oldarray = (ArrayType *) datum;
846                 Assert(null || ARR_NDIM(oldarray) == 1);
847                 /* first add the old array to the hitherto empty list */
848                 if (!null)
849                         for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
850                         {
851                                 int                     index,
852                                                         arrval;
853                                 Value      *v;
854                                 bool            valueNull;
855
856                                 index = i;
857                                 arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true /* by value */ ,
858                                                                                         sizeof(int), 0, &valueNull));
859                                 v = makeInteger(arrval);
860                                 /* filter out duplicates */
861                                 if (!member(v, newlist))
862                                         newlist = lcons(v, newlist);
863                         }
864
865                 /*
866                  * now convert the to be added usernames to sysids and add them to
867                  * the list
868                  */
869                 foreach(item, stmt->listUsers)
870                 {
871                         Value      *v;
872
873                         if (strcmp(tag, "ALTER GROUP") == 0)
874                         {
875                                 /* Get the uid of the proposed user to add. */
876                                 v = makeInteger(get_usesysid(strVal(lfirst(item))));
877                         }
878                         else if (strcmp(tag, "CREATE USER") == 0)
879                         {
880                                 /*
881                                  * in this case we already know the uid and it wouldn't be
882                                  * in the cache anyway yet
883                                  */
884                                 v = lfirst(item);
885                         }
886                         else
887                         {
888                                 elog(ERROR, "AlterGroup: unknown tag %s", tag);
889                                 v = NULL;               /* keep compiler quiet */
890                         }
891
892                         if (!member(v, newlist))
893                                 newlist = lcons(v, newlist);
894                         else
895                                 /*
896                                  * we silently assume here that this error will only come
897                                  * up in a ALTER GROUP statement
898                                  */
899                                 elog(NOTICE, "%s: user \"%s\" is already in group \"%s\"",
900                                          tag, strVal(lfirst(item)), stmt->name);
901                 }
902
903                 newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
904                 newarray->size = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
905                 newarray->flags = 0;
906                 ARR_NDIM(newarray) = 1; /* one dimensional array */
907                 ARR_LBOUND(newarray)[0] = 1;    /* axis starts at one */
908                 ARR_DIMS(newarray)[0] = length(newlist);                /* axis is this long */
909                 /* fill the array */
910                 i = 0;
911                 foreach(item, newlist)
912                         ((int *) ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));
913
914                 /*
915                  * Form a tuple with the new array and write it back.
916                  */
917                 new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
918                 new_record[Anum_pg_group_grosysid - 1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
919                 new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
920
921                 tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
922                 simple_heap_update(pg_group_rel, &group_tuple->t_self, tuple);
923
924                 /* Update indexes */
925                 if (RelationGetForm(pg_group_rel)->relhasindex)
926                 {
927                         Relation        idescs[Num_pg_group_indices];
928
929                         CatalogOpenIndices(Num_pg_group_indices,
930                                                            Name_pg_group_indices, idescs);
931                         CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
932                                                            tuple);
933                         CatalogCloseIndices(Num_pg_group_indices, idescs);
934                 }
935         }                                                       /* endif alter group add user */
936
937         else if (stmt->action == -1)/* drop users from group */
938         {
939                 Datum           datum;
940                 bool            null;
941                 bool            is_dropuser = strcmp(tag, "DROP USER") == 0;
942
943                 datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
944                 if (null)
945                 {
946                         if (!is_dropuser)
947                                 elog(NOTICE, "ALTER GROUP: group \"%s\" does not have any members", stmt->name);
948                 }
949                 else
950                 {
951                         HeapTuple       tuple;
952                         Datum           new_record[Natts_pg_group];
953                         char            new_record_nulls[Natts_pg_group] = {' ', ' ', ' '};
954                         ArrayType  *oldarray,
955                                            *newarray;
956                         List       *newlist = NULL,
957                                            *item;
958                         int                     i;
959
960                         oldarray = (ArrayType *) datum;
961                         Assert(ARR_NDIM(oldarray) == 1);
962                         /* first add the old array to the hitherto empty list */
963                         for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
964                         {
965                                 int                     index,
966                                                         arrval;
967                                 Value      *v;
968                                 bool            valueNull;
969
970                                 index = i;
971                                 arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true /* by value */ ,
972                                                                                         sizeof(int), 0, &valueNull));
973                                 v = makeInteger(arrval);
974                                 /* filter out duplicates */
975                                 if (!member(v, newlist))
976                                         newlist = lcons(v, newlist);
977                         }
978
979                         /*
980                          * now convert the to be dropped usernames to sysids and
981                          * remove them from the list
982                          */
983                         foreach(item, stmt->listUsers)
984                         {
985                                 Value      *v;
986
987                                 if (!is_dropuser)
988                                 {
989                                         /* Get the uid of the proposed user to drop. */
990                                         v = makeInteger(get_usesysid(strVal(lfirst(item))));
991                                 }
992                                 else
993                                 {
994                                         /* for dropuser we already know the uid */
995                                         v = lfirst(item);
996                                 }
997                                 if (member(v, newlist))
998                                         newlist = LispRemove(v, newlist);
999                                 else if (!is_dropuser)
1000                                         elog(NOTICE, "ALTER GROUP: user \"%s\" is not in group \"%s\"", strVal(lfirst(item)), stmt->name);
1001                         }
1002
1003                         newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
1004                         newarray->size = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
1005                         newarray->flags = 0;
1006                         ARR_NDIM(newarray) = 1;         /* one dimensional array */
1007                         ARR_LBOUND(newarray)[0] = 1;            /* axis starts at one */
1008                         ARR_DIMS(newarray)[0] = length(newlist);        /* axis is this long */
1009                         /* fill the array */
1010                         i = 0;
1011                         foreach(item, newlist)
1012                                 ((int *) ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));
1013
1014                         /*
1015                          * Insert the new tuple with the updated user list
1016                          */
1017                         new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
1018                         new_record[Anum_pg_group_grosysid - 1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
1019                         new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1020
1021                         tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
1022                         simple_heap_update(pg_group_rel, &group_tuple->t_self, tuple);
1023
1024                         /* Update indexes */
1025                         if (RelationGetForm(pg_group_rel)->relhasindex)
1026                         {
1027                                 Relation        idescs[Num_pg_group_indices];
1028
1029                                 CatalogOpenIndices(Num_pg_group_indices,
1030                                                                    Name_pg_group_indices, idescs);
1031                                 CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
1032                                                                    tuple);
1033                                 CatalogCloseIndices(Num_pg_group_indices, idescs);
1034                         }
1035
1036                 }                                               /* endif group not null */
1037         }                                                       /* endif alter group drop user */
1038
1039         ReleaseSysCache(group_tuple);
1040
1041         heap_close(pg_group_rel, NoLock);
1042 }
1043
1044
1045
1046 /*
1047  * DROP GROUP
1048  */
1049 void
1050 DropGroup(DropGroupStmt *stmt)
1051 {
1052         Relation        pg_group_rel;
1053         HeapScanDesc scan;
1054         HeapTuple       tuple;
1055         TupleDesc       pg_group_dsc;
1056         bool            gro_exists = false;
1057
1058         /*
1059          * Make sure the user can do this.
1060          */
1061         if (!superuser())
1062                 elog(ERROR, "DROP GROUP: permission denied");
1063
1064         /*
1065          * Scan the pg_group table and delete all matching groups.
1066          */
1067         pg_group_rel = heap_openr(GroupRelationName, ExclusiveLock);
1068         pg_group_dsc = RelationGetDescr(pg_group_rel);
1069         scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
1070
1071         while (HeapTupleIsValid(tuple = heap_getnext(scan, false)))
1072         {
1073                 Datum           datum;
1074                 bool            null;
1075
1076                 datum = heap_getattr(tuple, Anum_pg_group_groname,
1077                                                          pg_group_dsc, &null);
1078                 if (!null && strcmp((char *) DatumGetName(datum), stmt->name) == 0)
1079                 {
1080                         gro_exists = true;
1081                         simple_heap_delete(pg_group_rel, &tuple->t_self);
1082                 }
1083         }
1084
1085         heap_endscan(scan);
1086
1087         /*
1088          * Did we find any?
1089          */
1090         if (!gro_exists)
1091                 elog(ERROR, "DROP GROUP: group \"%s\" does not exist", stmt->name);
1092
1093         heap_close(pg_group_rel, NoLock);
1094 }