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