~vcs-imports/mammoth-replicator/trunk

« back to all changes in this revision

Viewing changes to src/backend/commands/user.c

  • Committer: alvherre
  • Date: 2005-12-16 21:24:52 UTC
  • Revision ID: svn-v4:db760fc0-0f08-0410-9d63-cc6633f64896:trunk:1
Initial import of the REL8_0_3 sources from the Pgsql CVS repository.

Show diffs side-by-side

added added

removed removed

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