]> granicus.if.org Git - postgresql/blob - src/backend/commands/user.c
For some reason access/tupmacs.h has been #including utils/memutils.h,
[postgresql] / src / backend / commands / user.c
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.151 2005/04/14 20:03:24 tgl Exp $
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
14
15 #include "access/heapam.h"
16 #include "catalog/indexing.h"
17 #include "catalog/pg_database.h"
18 #include "catalog/pg_group.h"
19 #include "catalog/pg_shadow.h"
20 #include "catalog/pg_type.h"
21 #include "commands/user.h"
22 #include "libpq/crypt.h"
23 #include "miscadmin.h"
24 #include "utils/acl.h"
25 #include "utils/builtins.h"
26 #include "utils/flatfiles.h"
27 #include "utils/fmgroids.h"
28 #include "utils/guc.h"
29 #include "utils/lsyscache.h"
30 #include "utils/syscache.h"
31
32
33 extern bool Password_encryption;
34
35
36 static void CheckPgUserAclNotNull(void);
37 static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
38                                           List *members);
39 static IdList *IdListToArray(List *members);
40 static List *IdArrayToList(IdList *oldarray);
41
42
43 /*
44  * CREATE USER
45  */
46 void
47 CreateUser(CreateUserStmt *stmt)
48 {
49         Relation        pg_shadow_rel;
50         TupleDesc       pg_shadow_dsc;
51         HeapScanDesc scan;
52         HeapTuple       tuple;
53         Datum           new_record[Natts_pg_shadow];
54         char            new_record_nulls[Natts_pg_shadow];
55         bool            user_exists = false,
56                                 sysid_exists = false,
57                                 havesysid = false;
58         int                     max_id;
59         ListCell   *item;
60         ListCell   *option;
61         char       *password = NULL;    /* PostgreSQL user password */
62         bool            encrypt_password = Password_encryption; /* encrypt password? */
63         char            encrypted_password[MD5_PASSWD_LEN + 1];
64         int                     sysid = 0;              /* PgSQL system id (valid if havesysid) */
65         bool            createdb = false;               /* Can the user create databases? */
66         bool            createuser = false;             /* Can this user create users? */
67         List       *groupElts = NIL;    /* The groups the user is a member of */
68         char       *validUntil = NULL;          /* The time the login is valid
69                                                                                  * until */
70         DefElem    *dpassword = NULL;
71         DefElem    *dsysid = NULL;
72         DefElem    *dcreatedb = NULL;
73         DefElem    *dcreateuser = NULL;
74         DefElem    *dgroupElts = NULL;
75         DefElem    *dvalidUntil = NULL;
76
77         /* Extract options from the statement node tree */
78         foreach(option, stmt->options)
79         {
80                 DefElem    *defel = (DefElem *) lfirst(option);
81
82                 if (strcmp(defel->defname, "password") == 0 ||
83                         strcmp(defel->defname, "encryptedPassword") == 0 ||
84                         strcmp(defel->defname, "unencryptedPassword") == 0)
85                 {
86                         if (dpassword)
87                                 ereport(ERROR,
88                                                 (errcode(ERRCODE_SYNTAX_ERROR),
89                                                  errmsg("conflicting or redundant options")));
90                         dpassword = defel;
91                         if (strcmp(defel->defname, "encryptedPassword") == 0)
92                                 encrypt_password = true;
93                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
94                                 encrypt_password = false;
95                 }
96                 else if (strcmp(defel->defname, "sysid") == 0)
97                 {
98                         if (dsysid)
99                                 ereport(ERROR,
100                                                 (errcode(ERRCODE_SYNTAX_ERROR),
101                                                  errmsg("conflicting or redundant options")));
102                         dsysid = defel;
103                 }
104                 else if (strcmp(defel->defname, "createdb") == 0)
105                 {
106                         if (dcreatedb)
107                                 ereport(ERROR,
108                                                 (errcode(ERRCODE_SYNTAX_ERROR),
109                                                  errmsg("conflicting or redundant options")));
110                         dcreatedb = defel;
111                 }
112                 else if (strcmp(defel->defname, "createuser") == 0)
113                 {
114                         if (dcreateuser)
115                                 ereport(ERROR,
116                                                 (errcode(ERRCODE_SYNTAX_ERROR),
117                                                  errmsg("conflicting or redundant options")));
118                         dcreateuser = defel;
119                 }
120                 else if (strcmp(defel->defname, "groupElts") == 0)
121                 {
122                         if (dgroupElts)
123                                 ereport(ERROR,
124                                                 (errcode(ERRCODE_SYNTAX_ERROR),
125                                                  errmsg("conflicting or redundant options")));
126                         dgroupElts = defel;
127                 }
128                 else if (strcmp(defel->defname, "validUntil") == 0)
129                 {
130                         if (dvalidUntil)
131                                 ereport(ERROR,
132                                                 (errcode(ERRCODE_SYNTAX_ERROR),
133                                                  errmsg("conflicting or redundant options")));
134                         dvalidUntil = defel;
135                 }
136                 else
137                         elog(ERROR, "option \"%s\" not recognized",
138                                  defel->defname);
139         }
140
141         if (dcreatedb)
142                 createdb = intVal(dcreatedb->arg) != 0;
143         if (dcreateuser)
144                 createuser = intVal(dcreateuser->arg) != 0;
145         if (dsysid)
146         {
147                 sysid = intVal(dsysid->arg);
148                 if (sysid <= 0)
149                         ereport(ERROR,
150                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
151                                          errmsg("user ID must be positive")));
152                 havesysid = true;
153         }
154         if (dvalidUntil)
155                 validUntil = strVal(dvalidUntil->arg);
156         if (dpassword)
157                 password = strVal(dpassword->arg);
158         if (dgroupElts)
159                 groupElts = (List *) dgroupElts->arg;
160
161         /* Check some permissions first */
162         if (password)
163                 CheckPgUserAclNotNull();
164
165         if (!superuser())
166                 ereport(ERROR,
167                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
168                                  errmsg("must be superuser to create users")));
169
170         if (strcmp(stmt->user, "public") == 0)
171                 ereport(ERROR,
172                                 (errcode(ERRCODE_RESERVED_NAME),
173                                  errmsg("user name \"%s\" is reserved",
174                                                 stmt->user)));
175
176         /*
177          * Scan the pg_shadow relation to be certain the user or id doesn't
178          * already exist.  Note we secure exclusive lock, because we also need
179          * to be sure of what the next usesysid should be, and we need to
180          * protect our eventual update of the flat password file.
181          */
182         pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock);
183         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
184
185         scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL);
186         max_id = 99;                            /* start auto-assigned ids at 100 */
187         while (!user_exists && !sysid_exists &&
188                    (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
189         {
190                 Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple);
191                 int32           this_sysid;
192
193                 user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0);
194
195                 this_sysid = shadow_form->usesysid;
196                 if (havesysid)                  /* customized id wanted */
197                         sysid_exists = (this_sysid == sysid);
198                 else
199                 {
200                         /* pick 1 + max */
201                         if (this_sysid > max_id)
202                                 max_id = this_sysid;
203                 }
204         }
205         heap_endscan(scan);
206
207         if (user_exists)
208                 ereport(ERROR,
209                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
210                                  errmsg("user \"%s\" already exists",
211                                                 stmt->user)));
212         if (sysid_exists)
213                 ereport(ERROR,
214                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
215                                  errmsg("user ID %d is already assigned", sysid)));
216
217         /* If no sysid given, use max existing id + 1 */
218         if (!havesysid)
219                 sysid = max_id + 1;
220
221         /*
222          * Build a tuple to insert
223          */
224         MemSet(new_record, 0, sizeof(new_record));
225         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
226
227         new_record[Anum_pg_shadow_usename - 1] =
228                 DirectFunctionCall1(namein, CStringGetDatum(stmt->user));
229         new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid);
230         AssertState(BoolIsValid(createdb));
231         new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb);
232         AssertState(BoolIsValid(createuser));
233         new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
234         /* superuser gets catupd right by default */
235         new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
236
237         if (password)
238         {
239                 if (!encrypt_password || isMD5(password))
240                         new_record[Anum_pg_shadow_passwd - 1] =
241                                 DirectFunctionCall1(textin, CStringGetDatum(password));
242                 else
243                 {
244                         if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
245                                                         encrypted_password))
246                                 elog(ERROR, "password encryption failed");
247                         new_record[Anum_pg_shadow_passwd - 1] =
248                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
249                 }
250         }
251         else
252                 new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n';
253
254         if (validUntil)
255                 new_record[Anum_pg_shadow_valuntil - 1] =
256                         DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
257         else
258                 new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n';
259
260         new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n';
261
262         tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
263
264         /*
265          * Insert new record in the pg_shadow table
266          */
267         simple_heap_insert(pg_shadow_rel, tuple);
268
269         /* Update indexes */
270         CatalogUpdateIndexes(pg_shadow_rel, tuple);
271
272         /*
273          * Add the user to the groups specified. We'll just call the below
274          * AlterGroup for this.
275          */
276         foreach(item, groupElts)
277         {
278                 AlterGroupStmt ags;
279
280                 ags.name = strVal(lfirst(item));                /* the group name to add
281                                                                                                  * this in */
282                 ags.action = +1;
283                 ags.listUsers = list_make1(makeInteger(sysid));
284                 AlterGroup(&ags, "CREATE USER");
285         }
286
287         /*
288          * Now we can clean up; but keep lock until commit (to avoid possible
289          * deadlock when commit code tries to acquire lock).
290          */
291         heap_close(pg_shadow_rel, NoLock);
292
293         /*
294          * Set flag to update flat password file at commit.
295          */
296         user_file_update_needed();
297 }
298
299
300
301 /*
302  * ALTER USER
303  */
304 void
305 AlterUser(AlterUserStmt *stmt)
306 {
307         Datum           new_record[Natts_pg_shadow];
308         char            new_record_nulls[Natts_pg_shadow];
309         char            new_record_repl[Natts_pg_shadow];
310         Relation        pg_shadow_rel;
311         TupleDesc       pg_shadow_dsc;
312         HeapTuple       tuple,
313                                 new_tuple;
314         ListCell   *option;
315         char       *password = NULL;    /* PostgreSQL user password */
316         bool            encrypt_password = Password_encryption; /* encrypt password? */
317         char            encrypted_password[MD5_PASSWD_LEN + 1];
318         int                     createdb = -1;  /* Can the user create databases? */
319         int                     createuser = -1;        /* Can this user create users? */
320         char       *validUntil = NULL;          /* The time the login is valid
321                                                                                  * until */
322         DefElem    *dpassword = NULL;
323         DefElem    *dcreatedb = NULL;
324         DefElem    *dcreateuser = NULL;
325         DefElem    *dvalidUntil = NULL;
326
327         /* Extract options from the statement node tree */
328         foreach(option, stmt->options)
329         {
330                 DefElem    *defel = (DefElem *) lfirst(option);
331
332                 if (strcmp(defel->defname, "password") == 0 ||
333                         strcmp(defel->defname, "encryptedPassword") == 0 ||
334                         strcmp(defel->defname, "unencryptedPassword") == 0)
335                 {
336                         if (dpassword)
337                                 ereport(ERROR,
338                                                 (errcode(ERRCODE_SYNTAX_ERROR),
339                                                  errmsg("conflicting or redundant options")));
340                         dpassword = defel;
341                         if (strcmp(defel->defname, "encryptedPassword") == 0)
342                                 encrypt_password = true;
343                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
344                                 encrypt_password = false;
345                 }
346                 else if (strcmp(defel->defname, "createdb") == 0)
347                 {
348                         if (dcreatedb)
349                                 ereport(ERROR,
350                                                 (errcode(ERRCODE_SYNTAX_ERROR),
351                                                  errmsg("conflicting or redundant options")));
352                         dcreatedb = defel;
353                 }
354                 else if (strcmp(defel->defname, "createuser") == 0)
355                 {
356                         if (dcreateuser)
357                                 ereport(ERROR,
358                                                 (errcode(ERRCODE_SYNTAX_ERROR),
359                                                  errmsg("conflicting or redundant options")));
360                         dcreateuser = defel;
361                 }
362                 else if (strcmp(defel->defname, "validUntil") == 0)
363                 {
364                         if (dvalidUntil)
365                                 ereport(ERROR,
366                                                 (errcode(ERRCODE_SYNTAX_ERROR),
367                                                  errmsg("conflicting or redundant options")));
368                         dvalidUntil = defel;
369                 }
370                 else
371                         elog(ERROR, "option \"%s\" not recognized",
372                                  defel->defname);
373         }
374
375         if (dcreatedb)
376                 createdb = intVal(dcreatedb->arg);
377         if (dcreateuser)
378                 createuser = intVal(dcreateuser->arg);
379         if (dvalidUntil)
380                 validUntil = strVal(dvalidUntil->arg);
381         if (dpassword)
382                 password = strVal(dpassword->arg);
383
384         if (password)
385                 CheckPgUserAclNotNull();
386
387         /* must be superuser or just want to change your own password */
388         if (!superuser() &&
389                 !(createdb < 0 &&
390                   createuser < 0 &&
391                   !validUntil &&
392                   password &&
393                   strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0))
394                 ereport(ERROR,
395                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
396                                  errmsg("permission denied")));
397
398         /*
399          * Scan the pg_shadow relation to be certain the user exists. Note we
400          * secure exclusive lock to protect our update of the flat password
401          * file.
402          */
403         pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock);
404         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
405
406         tuple = SearchSysCache(SHADOWNAME,
407                                                    PointerGetDatum(stmt->user),
408                                                    0, 0, 0);
409         if (!HeapTupleIsValid(tuple))
410                 ereport(ERROR,
411                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
412                                  errmsg("user \"%s\" does not exist", stmt->user)));
413
414         /*
415          * Build an updated tuple, perusing the information just obtained
416          */
417         MemSet(new_record, 0, sizeof(new_record));
418         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
419         MemSet(new_record_repl, ' ', sizeof(new_record_repl));
420
421         new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
422                                                                                         CStringGetDatum(stmt->user));
423         new_record_repl[Anum_pg_shadow_usename - 1] = 'r';
424
425         /* createdb */
426         if (createdb >= 0)
427         {
428                 new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
429                 new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
430         }
431
432         /*
433          * createuser (superuser) and catupd
434          *
435          * XXX It's rather unclear how to handle catupd.  It's probably best to
436          * keep it equal to the superuser status, otherwise you could end up
437          * with a situation where no existing superuser can alter the
438          * catalogs, including pg_shadow!
439          */
440         if (createuser >= 0)
441         {
442                 new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0);
443                 new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r';
444
445                 new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0);
446                 new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r';
447         }
448
449         /* password */
450         if (password)
451         {
452                 if (!encrypt_password || isMD5(password))
453                         new_record[Anum_pg_shadow_passwd - 1] =
454                                 DirectFunctionCall1(textin, CStringGetDatum(password));
455                 else
456                 {
457                         if (!EncryptMD5(password, stmt->user, strlen(stmt->user),
458                                                         encrypted_password))
459                                 elog(ERROR, "password encryption failed");
460                         new_record[Anum_pg_shadow_passwd - 1] =
461                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
462                 }
463                 new_record_repl[Anum_pg_shadow_passwd - 1] = 'r';
464         }
465
466         /* valid until */
467         if (validUntil)
468         {
469                 new_record[Anum_pg_shadow_valuntil - 1] =
470                         DirectFunctionCall1(abstimein, CStringGetDatum(validUntil));
471                 new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r';
472         }
473
474         new_tuple = heap_modifytuple(tuple, pg_shadow_dsc, new_record,
475                                                                  new_record_nulls, new_record_repl);
476         simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple);
477
478         /* Update indexes */
479         CatalogUpdateIndexes(pg_shadow_rel, new_tuple);
480
481         ReleaseSysCache(tuple);
482         heap_freetuple(new_tuple);
483
484         /*
485          * Now we can clean up; but keep lock until commit (to avoid possible
486          * deadlock when commit code tries to acquire lock).
487          */
488         heap_close(pg_shadow_rel, NoLock);
489
490         /*
491          * Set flag to update flat password file at commit.
492          */
493         user_file_update_needed();
494 }
495
496
497 /*
498  * ALTER USER ... SET
499  */
500 void
501 AlterUserSet(AlterUserSetStmt *stmt)
502 {
503         char       *valuestr;
504         HeapTuple       oldtuple,
505                                 newtuple;
506         Relation        rel;
507         Datum           repl_val[Natts_pg_shadow];
508         char            repl_null[Natts_pg_shadow];
509         char            repl_repl[Natts_pg_shadow];
510         int                     i;
511
512         valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
513
514         /*
515          * RowExclusiveLock is sufficient, because we don't need to update the
516          * flat password file.
517          */
518         rel = heap_open(ShadowRelationId, RowExclusiveLock);
519         oldtuple = SearchSysCache(SHADOWNAME,
520                                                           PointerGetDatum(stmt->user),
521                                                           0, 0, 0);
522         if (!HeapTupleIsValid(oldtuple))
523                 ereport(ERROR,
524                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
525                                  errmsg("user \"%s\" does not exist", stmt->user)));
526
527         if (!(superuser() ||
528                 ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId()))
529                 ereport(ERROR,
530                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
531                                  errmsg("permission denied")));
532
533         for (i = 0; i < Natts_pg_shadow; i++)
534                 repl_repl[i] = ' ';
535
536         repl_repl[Anum_pg_shadow_useconfig - 1] = 'r';
537         if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
538         {
539                 /* RESET ALL */
540                 repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
541         }
542         else
543         {
544                 Datum           datum;
545                 bool            isnull;
546                 ArrayType  *array;
547
548                 repl_null[Anum_pg_shadow_useconfig - 1] = ' ';
549
550                 datum = SysCacheGetAttr(SHADOWNAME, oldtuple,
551                                                                 Anum_pg_shadow_useconfig, &isnull);
552
553                 array = isnull ? NULL : DatumGetArrayTypeP(datum);
554
555                 if (valuestr)
556                         array = GUCArrayAdd(array, stmt->variable, valuestr);
557                 else
558                         array = GUCArrayDelete(array, stmt->variable);
559
560                 if (array)
561                         repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array);
562                 else
563                         repl_null[Anum_pg_shadow_useconfig - 1] = 'n';
564         }
565
566         newtuple = heap_modifytuple(oldtuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
567         simple_heap_update(rel, &oldtuple->t_self, newtuple);
568
569         CatalogUpdateIndexes(rel, newtuple);
570
571         ReleaseSysCache(oldtuple);
572         heap_close(rel, RowExclusiveLock);
573 }
574
575
576 /*
577  * DROP USER
578  */
579 void
580 DropUser(DropUserStmt *stmt)
581 {
582         Relation        pg_shadow_rel;
583         TupleDesc       pg_shadow_dsc;
584         ListCell   *item;
585
586         if (!superuser())
587                 ereport(ERROR,
588                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
589                                  errmsg("must be superuser to drop users")));
590
591         /*
592          * Scan the pg_shadow relation to find the usesysid of the user to be
593          * deleted.  Note we secure exclusive lock, because we need to protect
594          * our update of the flat password file.
595          */
596         pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock);
597         pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
598
599         foreach(item, stmt->users)
600         {
601                 const char *user = strVal(lfirst(item));
602                 HeapTuple       tuple,
603                                         tmp_tuple;
604                 Relation        pg_rel;
605                 TupleDesc       pg_dsc;
606                 ScanKeyData scankey;
607                 HeapScanDesc scan;
608                 AclId           usesysid;
609
610                 tuple = SearchSysCache(SHADOWNAME,
611                                                            PointerGetDatum(user),
612                                                            0, 0, 0);
613                 if (!HeapTupleIsValid(tuple))
614                         ereport(ERROR,
615                                         (errcode(ERRCODE_UNDEFINED_OBJECT),
616                                          errmsg("user \"%s\" does not exist", user)));
617
618                 usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid;
619
620                 if (usesysid == GetUserId())
621                         ereport(ERROR,
622                                         (errcode(ERRCODE_OBJECT_IN_USE),
623                                          errmsg("current user cannot be dropped")));
624                 if (usesysid == GetSessionUserId())
625                         ereport(ERROR,
626                                         (errcode(ERRCODE_OBJECT_IN_USE),
627                                          errmsg("session user cannot be dropped")));
628
629                 /*
630                  * Check if user still owns a database. If so, error out.
631                  *
632                  * (It used to be that this function would drop the database
633                  * automatically. This is not only very dangerous for people that
634                  * don't read the manual, it doesn't seem to be the behaviour one
635                  * would expect either.) -- petere 2000/01/14)
636                  */
637                 pg_rel = heap_open(DatabaseRelationId, AccessShareLock);
638                 pg_dsc = RelationGetDescr(pg_rel);
639
640                 ScanKeyInit(&scankey,
641                                         Anum_pg_database_datdba,
642                                         BTEqualStrategyNumber, F_INT4EQ,
643                                         Int32GetDatum(usesysid));
644
645                 scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
646
647                 if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
648                 {
649                         char       *dbname;
650
651                         dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
652                         ereport(ERROR,
653                                         (errcode(ERRCODE_OBJECT_IN_USE),
654                                          errmsg("user \"%s\" cannot be dropped", user),
655                                    errdetail("The user owns database \"%s\".", dbname)));
656                 }
657
658                 heap_endscan(scan);
659                 heap_close(pg_rel, AccessShareLock);
660
661                 /*
662                  * Somehow we'd have to check for tables, views, etc. owned by the
663                  * user as well, but those could be spread out over all sorts of
664                  * databases which we don't have access to (easily).
665                  */
666
667                 /*
668                  * Remove the user from the pg_shadow table
669                  */
670                 simple_heap_delete(pg_shadow_rel, &tuple->t_self);
671
672                 ReleaseSysCache(tuple);
673
674                 /*
675                  * Remove user from groups
676                  *
677                  * try calling alter group drop user for every group
678                  */
679                 pg_rel = heap_open(GroupRelationId, ExclusiveLock);
680                 pg_dsc = RelationGetDescr(pg_rel);
681                 scan = heap_beginscan(pg_rel, SnapshotNow, 0, NULL);
682                 while ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
683                 {
684                         AlterGroupStmt ags;
685
686                         /* the group name from which to try to drop the user: */
687                         ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname));
688                         ags.action = -1;
689                         ags.listUsers = list_make1(makeInteger(usesysid));
690                         AlterGroup(&ags, "DROP USER");
691                 }
692                 heap_endscan(scan);
693                 heap_close(pg_rel, ExclusiveLock);
694
695                 /*
696                  * Advance command counter so that later iterations of this loop
697                  * will see the changes already made.  This is essential if, for
698                  * example, we are trying to drop two users who are members of the
699                  * same group --- the AlterGroup for the second user had better
700                  * see the tuple updated from the first one.
701                  */
702                 CommandCounterIncrement();
703         }
704
705         /*
706          * Now we can clean up; but keep lock until commit (to avoid possible
707          * deadlock when commit code tries to acquire lock).
708          */
709         heap_close(pg_shadow_rel, NoLock);
710
711         /*
712          * Set flag to update flat password file at commit.
713          */
714         user_file_update_needed();
715 }
716
717
718 /*
719  * Rename user
720  */
721 void
722 RenameUser(const char *oldname, const char *newname)
723 {
724         HeapTuple       oldtuple,
725                                 newtuple;
726         TupleDesc       dsc;
727         Relation        rel;
728         Datum           datum;
729         bool            isnull;
730         Datum           repl_val[Natts_pg_shadow];
731         char            repl_null[Natts_pg_shadow];
732         char            repl_repl[Natts_pg_shadow];
733         int                     i;
734
735         /* ExclusiveLock because we need to update the password file */
736         rel = heap_open(ShadowRelationId, ExclusiveLock);
737         dsc = RelationGetDescr(rel);
738
739         oldtuple = SearchSysCache(SHADOWNAME,
740                                                           CStringGetDatum(oldname),
741                                                           0, 0, 0);
742         if (!HeapTupleIsValid(oldtuple))
743                 ereport(ERROR,
744                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
745                                  errmsg("user \"%s\" does not exist", oldname)));
746
747         /*
748          * XXX Client applications probably store the session user somewhere,
749          * so renaming it could cause confusion.  On the other hand, there may
750          * not be an actual problem besides a little confusion, so think about
751          * this and decide.
752          */
753         if (((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetSessionUserId())
754                 ereport(ERROR,
755                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
756                                  errmsg("session user may not be renamed")));
757
758         /* make sure the new name doesn't exist */
759         if (SearchSysCacheExists(SHADOWNAME,
760                                                          CStringGetDatum(newname),
761                                                          0, 0, 0))
762                 ereport(ERROR,
763                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
764                                  errmsg("user \"%s\" already exists", newname)));
765
766         /* must be superuser */
767         if (!superuser())
768                 ereport(ERROR,
769                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
770                                  errmsg("must be superuser to rename users")));
771
772         for (i = 0; i < Natts_pg_shadow; i++)
773                 repl_repl[i] = ' ';
774
775         repl_repl[Anum_pg_shadow_usename - 1] = 'r';
776         repl_val[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein,
777                                                                                            CStringGetDatum(newname));
778         repl_null[Anum_pg_shadow_usename - 1] = ' ';
779
780         datum = heap_getattr(oldtuple, Anum_pg_shadow_passwd, dsc, &isnull);
781
782         if (!isnull && isMD5(DatumGetCString(DirectFunctionCall1(textout, datum))))
783         {
784                 /* MD5 uses the username as salt, so just clear it on a rename */
785                 repl_repl[Anum_pg_shadow_passwd - 1] = 'r';
786                 repl_null[Anum_pg_shadow_passwd - 1] = 'n';
787
788                 ereport(NOTICE,
789                                 (errmsg("MD5 password cleared because of user rename")));
790         }
791
792         newtuple = heap_modifytuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
793         simple_heap_update(rel, &oldtuple->t_self, newtuple);
794
795         CatalogUpdateIndexes(rel, newtuple);
796
797         ReleaseSysCache(oldtuple);
798         heap_close(rel, NoLock);
799
800         user_file_update_needed();
801 }
802
803
804 /*
805  * CheckPgUserAclNotNull
806  *
807  * check to see if there is an ACL on pg_shadow
808  */
809 static void
810 CheckPgUserAclNotNull(void)
811 {
812         HeapTuple       htup;
813
814         htup = SearchSysCache(RELOID,
815                                                   ObjectIdGetDatum(ShadowRelationId),
816                                                   0, 0, 0);
817         if (!HeapTupleIsValid(htup))    /* should not happen, we hope */
818                 elog(ERROR, "cache lookup failed for relation %u", ShadowRelationId);
819
820         if (heap_attisnull(htup, Anum_pg_class_relacl))
821         {
822                 Form_pg_class classForm = (Form_pg_class) GETSTRUCT(htup);
823
824                 ereport(ERROR,
825                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
826                                  errmsg("before using passwords you must revoke privileges on %s",
827                                                 NameStr(classForm->relname)),
828                                  errdetail("This restriction is to prevent unprivileged users from reading the passwords."),
829                                  errhint("Try REVOKE ALL ON \"%s\" FROM PUBLIC.",
830                                                  NameStr(classForm->relname))));
831         }
832
833         ReleaseSysCache(htup);
834 }
835
836
837 /*
838  * CREATE GROUP
839  */
840 void
841 CreateGroup(CreateGroupStmt *stmt)
842 {
843         Relation        pg_group_rel;
844         HeapScanDesc scan;
845         HeapTuple       tuple;
846         TupleDesc       pg_group_dsc;
847         bool            group_exists = false,
848                                 sysid_exists = false,
849                                 havesysid = false;
850         int                     max_id;
851         Datum           new_record[Natts_pg_group];
852         char            new_record_nulls[Natts_pg_group];
853         ListCell   *item;
854         ListCell   *option;
855         List       *newlist = NIL;
856         IdList     *grolist;
857         int                     sysid = 0;
858         List       *userElts = NIL;
859         DefElem    *dsysid = NULL;
860         DefElem    *duserElts = NULL;
861
862         foreach(option, stmt->options)
863         {
864                 DefElem    *defel = (DefElem *) lfirst(option);
865
866                 if (strcmp(defel->defname, "sysid") == 0)
867                 {
868                         if (dsysid)
869                                 ereport(ERROR,
870                                                 (errcode(ERRCODE_SYNTAX_ERROR),
871                                                  errmsg("conflicting or redundant options")));
872                         dsysid = defel;
873                 }
874                 else if (strcmp(defel->defname, "userElts") == 0)
875                 {
876                         if (duserElts)
877                                 ereport(ERROR,
878                                                 (errcode(ERRCODE_SYNTAX_ERROR),
879                                                  errmsg("conflicting or redundant options")));
880                         duserElts = defel;
881                 }
882                 else
883                         elog(ERROR, "option \"%s\" not recognized",
884                                  defel->defname);
885         }
886
887         if (dsysid)
888         {
889                 sysid = intVal(dsysid->arg);
890                 if (sysid <= 0)
891                         ereport(ERROR,
892                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
893                                          errmsg("group ID must be positive")));
894                 havesysid = true;
895         }
896
897         if (duserElts)
898                 userElts = (List *) duserElts->arg;
899
900         /*
901          * Make sure the user can do this.
902          */
903         if (!superuser())
904                 ereport(ERROR,
905                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
906                                  errmsg("must be superuser to create groups")));
907
908         if (strcmp(stmt->name, "public") == 0)
909                 ereport(ERROR,
910                                 (errcode(ERRCODE_RESERVED_NAME),
911                                  errmsg("group name \"%s\" is reserved",
912                                                 stmt->name)));
913
914         /*
915          * Scan the pg_group relation to be certain the group or id doesn't
916          * already exist.  Note we secure exclusive lock, because we also need
917          * to be sure of what the next grosysid should be, and we need to
918          * protect our eventual update of the flat group file.
919          */
920         pg_group_rel = heap_open(GroupRelationId, ExclusiveLock);
921         pg_group_dsc = RelationGetDescr(pg_group_rel);
922
923         scan = heap_beginscan(pg_group_rel, SnapshotNow, 0, NULL);
924         max_id = 99;                            /* start auto-assigned ids at 100 */
925         while (!group_exists && !sysid_exists &&
926                    (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
927         {
928                 Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple);
929                 int32           this_sysid;
930
931                 group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0);
932
933                 this_sysid = group_form->grosysid;
934                 if (havesysid)                  /* customized id wanted */
935                         sysid_exists = (this_sysid == sysid);
936                 else
937                 {
938                         /* pick 1 + max */
939                         if (this_sysid > max_id)
940                                 max_id = this_sysid;
941                 }
942         }
943         heap_endscan(scan);
944
945         if (group_exists)
946                 ereport(ERROR,
947                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
948                                  errmsg("group \"%s\" already exists",
949                                                 stmt->name)));
950         if (sysid_exists)
951                 ereport(ERROR,
952                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
953                                  errmsg("group ID %d is already assigned", sysid)));
954
955         /* If no sysid given, use max existing id + 1 */
956         if (!havesysid)
957                 sysid = max_id + 1;
958
959         /*
960          * Translate the given user names to ids
961          */
962         foreach(item, userElts)
963         {
964                 const char *groupuser = strVal(lfirst(item));
965                 int32           userid = get_usesysid(groupuser);
966
967                 if (!list_member_int(newlist, userid))
968                         newlist = lappend_int(newlist, userid);
969         }
970
971         /* build an array to insert */
972         if (newlist)
973                 grolist = IdListToArray(newlist);
974         else
975                 grolist = NULL;
976
977         /*
978          * Form a tuple to insert
979          */
980         new_record[Anum_pg_group_groname - 1] =
981                 DirectFunctionCall1(namein, CStringGetDatum(stmt->name));
982         new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid);
983         new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist);
984
985         new_record_nulls[Anum_pg_group_groname - 1] = ' ';
986         new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
987         new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n';
988
989         tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
990
991         /*
992          * Insert a new record in the pg_group table
993          */
994         simple_heap_insert(pg_group_rel, tuple);
995
996         /* Update indexes */
997         CatalogUpdateIndexes(pg_group_rel, tuple);
998
999         /*
1000          * Now we can clean up; but keep lock until commit (to avoid possible
1001          * deadlock when commit code tries to acquire lock).
1002          */
1003         heap_close(pg_group_rel, NoLock);
1004
1005         /*
1006          * Set flag to update flat group file at commit.
1007          */
1008         group_file_update_needed();
1009 }
1010
1011
1012 /*
1013  * ALTER GROUP
1014  */
1015 void
1016 AlterGroup(AlterGroupStmt *stmt, const char *tag)
1017 {
1018         Relation        pg_group_rel;
1019         TupleDesc       pg_group_dsc;
1020         HeapTuple       group_tuple;
1021         IdList     *oldarray;
1022         Datum           datum;
1023         bool            null;
1024         List       *newlist;
1025         ListCell   *item;
1026
1027         /*
1028          * Make sure the user can do this.
1029          */
1030         if (!superuser())
1031                 ereport(ERROR,
1032                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1033                                  errmsg("must be superuser to alter groups")));
1034
1035         /*
1036          * Secure exclusive lock to protect our update of the flat group file.
1037          */
1038         pg_group_rel = heap_open(GroupRelationId, ExclusiveLock);
1039         pg_group_dsc = RelationGetDescr(pg_group_rel);
1040
1041         /*
1042          * Fetch existing tuple for group.
1043          */
1044         group_tuple = SearchSysCache(GRONAME,
1045                                                                  PointerGetDatum(stmt->name),
1046                                                                  0, 0, 0);
1047         if (!HeapTupleIsValid(group_tuple))
1048                 ereport(ERROR,
1049                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1050                                  errmsg("group \"%s\" does not exist", stmt->name)));
1051
1052         /* Fetch old group membership. */
1053         datum = heap_getattr(group_tuple, Anum_pg_group_grolist,
1054                                                  pg_group_dsc, &null);
1055         oldarray = null ? NULL : DatumGetIdListP(datum);
1056
1057         /* initialize list with old array contents */
1058         newlist = IdArrayToList(oldarray);
1059
1060         /*
1061          * Now decide what to do.
1062          */
1063         AssertState(stmt->action == +1 || stmt->action == -1);
1064
1065         if (stmt->action == +1)         /* add users, might also be invoked by
1066                                                                  * create user */
1067         {
1068                 /*
1069                  * convert the to be added usernames to sysids and add them to the
1070                  * list
1071                  */
1072                 foreach(item, stmt->listUsers)
1073                 {
1074                         int32           sysid;
1075
1076                         if (strcmp(tag, "ALTER GROUP") == 0)
1077                         {
1078                                 /* Get the uid of the proposed user to add. */
1079                                 sysid = get_usesysid(strVal(lfirst(item)));
1080                         }
1081                         else if (strcmp(tag, "CREATE USER") == 0)
1082                         {
1083                                 /*
1084                                  * in this case we already know the uid and it wouldn't be
1085                                  * in the cache anyway yet
1086                                  */
1087                                 sysid = intVal(lfirst(item));
1088                         }
1089                         else
1090                         {
1091                                 elog(ERROR, "unexpected tag: \"%s\"", tag);
1092                                 sysid = 0;              /* keep compiler quiet */
1093                         }
1094
1095                         if (!list_member_int(newlist, sysid))
1096                                 newlist = lappend_int(newlist, sysid);
1097                 }
1098
1099                 /* Do the update */
1100                 UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1101         }                                                       /* endif alter group add user */
1102
1103         else if (stmt->action == -1)    /* drop users from group */
1104         {
1105                 bool            is_dropuser = strcmp(tag, "DROP USER") == 0;
1106
1107                 if (newlist == NIL)
1108                 {
1109                         if (!is_dropuser)
1110                                 ereport(WARNING,
1111                                                 (errcode(ERRCODE_WARNING),
1112                                                  errmsg("group \"%s\" does not have any members",
1113                                                                 stmt->name)));
1114                 }
1115                 else
1116                 {
1117                         /*
1118                          * convert the to be dropped usernames to sysids and remove
1119                          * them from the list
1120                          */
1121                         foreach(item, stmt->listUsers)
1122                         {
1123                                 int32           sysid;
1124
1125                                 if (!is_dropuser)
1126                                 {
1127                                         /* Get the uid of the proposed user to drop. */
1128                                         sysid = get_usesysid(strVal(lfirst(item)));
1129                                 }
1130                                 else
1131                                 {
1132                                         /* for dropuser we already know the uid */
1133                                         sysid = intVal(lfirst(item));
1134                                 }
1135                                 if (list_member_int(newlist, sysid))
1136                                         newlist = list_delete_int(newlist, sysid);
1137                                 else if (!is_dropuser)
1138                                         ereport(WARNING,
1139                                                         (errcode(ERRCODE_WARNING),
1140                                                          errmsg("user \"%s\" is not in group \"%s\"",
1141                                                                         strVal(lfirst(item)), stmt->name)));
1142                         }
1143
1144                         /* Do the update */
1145                         UpdateGroupMembership(pg_group_rel, group_tuple, newlist);
1146                 }                                               /* endif group not null */
1147         }                                                       /* endif alter group drop user */
1148
1149         ReleaseSysCache(group_tuple);
1150
1151         /*
1152          * Now we can clean up; but keep lock until commit (to avoid possible
1153          * deadlock when commit code tries to acquire lock).
1154          */
1155         heap_close(pg_group_rel, NoLock);
1156
1157         /*
1158          * Set flag to update flat group file at commit.
1159          */
1160         group_file_update_needed();
1161 }
1162
1163 /*
1164  * Subroutine for AlterGroup: given a pg_group tuple and a desired new
1165  * membership (expressed as an integer list), form and write an updated tuple.
1166  * The pg_group relation must be open and locked already.
1167  */
1168 static void
1169 UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple,
1170                                           List *members)
1171 {
1172         IdList     *newarray;
1173         Datum           new_record[Natts_pg_group];
1174         char            new_record_nulls[Natts_pg_group];
1175         char            new_record_repl[Natts_pg_group];
1176         HeapTuple       tuple;
1177
1178         newarray = IdListToArray(members);
1179
1180         /*
1181          * Form an updated tuple with the new array and write it back.
1182          */
1183         MemSet(new_record, 0, sizeof(new_record));
1184         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1185         MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1186
1187         new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);
1188         new_record_repl[Anum_pg_group_grolist - 1] = 'r';
1189
1190         tuple = heap_modifytuple(group_tuple, RelationGetDescr(group_rel),
1191                                                   new_record, new_record_nulls, new_record_repl);
1192
1193         simple_heap_update(group_rel, &group_tuple->t_self, tuple);
1194
1195         /* Update indexes */
1196         CatalogUpdateIndexes(group_rel, tuple);
1197 }
1198
1199
1200 /*
1201  * Convert an integer list of sysids to an array.
1202  */
1203 static IdList *
1204 IdListToArray(List *members)
1205 {
1206         int                     nmembers = list_length(members);
1207         IdList     *newarray;
1208         ListCell   *item;
1209         int                     i;
1210
1211         newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32));
1212         newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32);
1213         newarray->flags = 0;
1214         newarray->elemtype = INT4OID;
1215         ARR_NDIM(newarray) = 1;         /* one dimensional array */
1216         ARR_LBOUND(newarray)[0] = 1;    /* axis starts at one */
1217         ARR_DIMS(newarray)[0] = nmembers;       /* axis is this long */
1218         i = 0;
1219         foreach(item, members)
1220                 ((int *) ARR_DATA_PTR(newarray))[i++] = lfirst_int(item);
1221
1222         return newarray;
1223 }
1224
1225 /*
1226  * Convert an array of sysids to an integer list.
1227  */
1228 static List *
1229 IdArrayToList(IdList *oldarray)
1230 {
1231         List       *newlist = NIL;
1232         int                     hibound,
1233                                 i;
1234
1235         if (oldarray == NULL)
1236                 return NIL;
1237
1238         Assert(ARR_NDIM(oldarray) == 1);
1239         Assert(ARR_ELEMTYPE(oldarray) == INT4OID);
1240
1241         hibound = ARR_DIMS(oldarray)[0];
1242
1243         for (i = 0; i < hibound; i++)
1244         {
1245                 int32           sysid;
1246
1247                 sysid = ((int32 *) ARR_DATA_PTR(oldarray))[i];
1248                 /* filter out any duplicates --- probably a waste of time */
1249                 if (!list_member_int(newlist, sysid))
1250                         newlist = lappend_int(newlist, sysid);
1251         }
1252
1253         return newlist;
1254 }
1255
1256
1257 /*
1258  * DROP GROUP
1259  */
1260 void
1261 DropGroup(DropGroupStmt *stmt)
1262 {
1263         Relation        pg_group_rel;
1264         HeapTuple       tuple;
1265
1266         /*
1267          * Make sure the user can do this.
1268          */
1269         if (!superuser())
1270                 ereport(ERROR,
1271                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1272                                  errmsg("must be superuser to drop groups")));
1273
1274         /*
1275          * Secure exclusive lock to protect our update of the flat group file.
1276          */
1277         pg_group_rel = heap_open(GroupRelationId, ExclusiveLock);
1278
1279         /* Find and delete the group. */
1280
1281         tuple = SearchSysCacheCopy(GRONAME,
1282                                                            PointerGetDatum(stmt->name),
1283                                                            0, 0, 0);
1284         if (!HeapTupleIsValid(tuple))
1285                 ereport(ERROR,
1286                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1287                                  errmsg("group \"%s\" does not exist", stmt->name)));
1288
1289         simple_heap_delete(pg_group_rel, &tuple->t_self);
1290
1291         /*
1292          * Now we can clean up; but keep lock until commit (to avoid possible
1293          * deadlock when commit code tries to acquire lock).
1294          */
1295         heap_close(pg_group_rel, NoLock);
1296
1297         /*
1298          * Set flag to update flat group file at commit.
1299          */
1300         group_file_update_needed();
1301 }
1302
1303
1304 /*
1305  * Rename group
1306  */
1307 void
1308 RenameGroup(const char *oldname, const char *newname)
1309 {
1310         HeapTuple       tup;
1311         Relation        rel;
1312
1313         /* ExclusiveLock because we need to update the flat group file */
1314         rel = heap_open(GroupRelationId, ExclusiveLock);
1315
1316         tup = SearchSysCacheCopy(GRONAME,
1317                                                          CStringGetDatum(oldname),
1318                                                          0, 0, 0);
1319         if (!HeapTupleIsValid(tup))
1320                 ereport(ERROR,
1321                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1322                                  errmsg("group \"%s\" does not exist", oldname)));
1323
1324         /* make sure the new name doesn't exist */
1325         if (SearchSysCacheExists(GRONAME,
1326                                                          CStringGetDatum(newname),
1327                                                          0, 0, 0))
1328                 ereport(ERROR,
1329                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
1330                                  errmsg("group \"%s\" already exists", newname)));
1331
1332         /* must be superuser */
1333         if (!superuser())
1334                 ereport(ERROR,
1335                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1336                                  errmsg("must be superuser to rename groups")));
1337
1338         /* rename */
1339         namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname);
1340         simple_heap_update(rel, &tup->t_self, tup);
1341         CatalogUpdateIndexes(rel, tup);
1342
1343         heap_close(rel, NoLock);
1344         heap_freetuple(tup);
1345
1346         group_file_update_needed();
1347 }