]> granicus.if.org Git - postgresql/blob - src/backend/commands/user.c
Minor correction: cause ALTER ROLE role ROLE rolenames to behave
[postgresql] / src / backend / commands / user.c
1 /*-------------------------------------------------------------------------
2  *
3  * user.c
4  *        Commands for manipulating roles (formerly called users).
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.159 2005/07/26 22:37:49 tgl Exp $
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
14
15 #include "access/genam.h"
16 #include "access/heapam.h"
17 #include "catalog/dependency.h"
18 #include "catalog/indexing.h"
19 #include "catalog/pg_auth_members.h"
20 #include "catalog/pg_authid.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 static List *roleNamesToIds(List *memberNames);
36 static void AddRoleMems(const char *rolename, Oid roleid,
37                                                 List *memberNames, List *memberIds,
38                                                 Oid grantorId, bool admin_opt);
39 static void DelRoleMems(const char *rolename, Oid roleid,
40                                                 List *memberNames, List *memberIds,
41                                                 bool admin_opt);
42
43
44 /* Check if current user has createrole privileges */
45 static bool
46 have_createrole_privilege(void)
47 {
48         bool            result = false;
49         HeapTuple       utup;
50
51         /* Superusers can always do everything */
52         if (superuser())
53                 return true;
54
55         utup = SearchSysCache(AUTHOID,
56                                                   ObjectIdGetDatum(GetUserId()),
57                                                   0, 0, 0);
58         if (HeapTupleIsValid(utup))
59         {
60                 result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
61                 ReleaseSysCache(utup);
62         }
63         return result;
64 }
65
66
67 /*
68  * CREATE ROLE
69  */
70 void
71 CreateRole(CreateRoleStmt *stmt)
72 {
73         Relation        pg_authid_rel;
74         TupleDesc       pg_authid_dsc;
75         HeapTuple       tuple;
76         Datum           new_record[Natts_pg_authid];
77         char            new_record_nulls[Natts_pg_authid];
78         Oid                     roleid;
79         ListCell   *item;
80         ListCell   *option;
81         char       *password = NULL;            /* user password */
82         bool            encrypt_password = Password_encryption; /* encrypt password? */
83         char            encrypted_password[MD5_PASSWD_LEN + 1];
84         bool            issuper = false;                /* Make the user a superuser? */
85         bool            inherit = true;                 /* Auto inherit privileges? */
86         bool            createrole = false;             /* Can this user create roles? */
87         bool            createdb = false;               /* Can the user create databases? */
88         bool            canlogin = false;               /* Can this user login? */
89         List       *addroleto = NIL;            /* roles to make this a member of */
90         List       *rolemembers = NIL;          /* roles to be members of this role */
91         List       *adminmembers = NIL;         /* roles to be admins of this role */
92         char       *validUntil = NULL;          /* time the login is valid until */
93         DefElem    *dpassword = NULL;
94         DefElem    *dissuper = NULL;
95         DefElem    *dinherit = NULL;
96         DefElem    *dcreaterole = NULL;
97         DefElem    *dcreatedb = NULL;
98         DefElem    *dcanlogin = NULL;
99         DefElem    *daddroleto = NULL;
100         DefElem    *drolemembers = NULL;
101         DefElem    *dadminmembers = NULL;
102         DefElem    *dvalidUntil = NULL;
103
104         /* The defaults can vary depending on the original statement type */
105         switch (stmt->stmt_type)
106         {
107                 case ROLESTMT_ROLE:
108                         break;
109                 case ROLESTMT_USER:
110                         canlogin = true;
111                         /* may eventually want inherit to default to false here */
112                         break;
113                 case ROLESTMT_GROUP:
114                         break;
115         }
116
117         /* Extract options from the statement node tree */
118         foreach(option, stmt->options)
119         {
120                 DefElem    *defel = (DefElem *) lfirst(option);
121
122                 if (strcmp(defel->defname, "password") == 0 ||
123                         strcmp(defel->defname, "encryptedPassword") == 0 ||
124                         strcmp(defel->defname, "unencryptedPassword") == 0)
125                 {
126                         if (dpassword)
127                                 ereport(ERROR,
128                                                 (errcode(ERRCODE_SYNTAX_ERROR),
129                                                  errmsg("conflicting or redundant options")));
130                         dpassword = defel;
131                         if (strcmp(defel->defname, "encryptedPassword") == 0)
132                                 encrypt_password = true;
133                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
134                                 encrypt_password = false;
135                 }
136                 else if (strcmp(defel->defname, "sysid") == 0)
137                 {
138                         ereport(NOTICE,
139                                         (errmsg("SYSID can no longer be specified")));
140                 }
141                 else if (strcmp(defel->defname, "superuser") == 0)
142                 {
143                         if (dissuper)
144                                 ereport(ERROR,
145                                                 (errcode(ERRCODE_SYNTAX_ERROR),
146                                                  errmsg("conflicting or redundant options")));
147                         dissuper = defel;
148                 }
149                 else if (strcmp(defel->defname, "inherit") == 0)
150                 {
151                         if (dinherit)
152                                 ereport(ERROR,
153                                                 (errcode(ERRCODE_SYNTAX_ERROR),
154                                                  errmsg("conflicting or redundant options")));
155                         dinherit = defel;
156                 }
157                 else if (strcmp(defel->defname, "createrole") == 0)
158                 {
159                         if (dcreaterole)
160                                 ereport(ERROR,
161                                                 (errcode(ERRCODE_SYNTAX_ERROR),
162                                                  errmsg("conflicting or redundant options")));
163                         dcreaterole = defel;
164                 }
165                 else if (strcmp(defel->defname, "createdb") == 0)
166                 {
167                         if (dcreatedb)
168                                 ereport(ERROR,
169                                                 (errcode(ERRCODE_SYNTAX_ERROR),
170                                                  errmsg("conflicting or redundant options")));
171                         dcreatedb = defel;
172                 }
173                 else if (strcmp(defel->defname, "canlogin") == 0)
174                 {
175                         if (dcanlogin)
176                                 ereport(ERROR,
177                                                 (errcode(ERRCODE_SYNTAX_ERROR),
178                                                  errmsg("conflicting or redundant options")));
179                         dcanlogin = defel;
180                 }
181                 else if (strcmp(defel->defname, "addroleto") == 0)
182                 {
183                         if (daddroleto)
184                                 ereport(ERROR,
185                                                 (errcode(ERRCODE_SYNTAX_ERROR),
186                                                  errmsg("conflicting or redundant options")));
187                         daddroleto = defel;
188                 }
189                 else if (strcmp(defel->defname, "rolemembers") == 0)
190                 {
191                         if (drolemembers)
192                                 ereport(ERROR,
193                                                 (errcode(ERRCODE_SYNTAX_ERROR),
194                                                  errmsg("conflicting or redundant options")));
195                         drolemembers = defel;
196                 }
197                 else if (strcmp(defel->defname, "adminmembers") == 0)
198                 {
199                         if (dadminmembers)
200                                 ereport(ERROR,
201                                                 (errcode(ERRCODE_SYNTAX_ERROR),
202                                                  errmsg("conflicting or redundant options")));
203                         dadminmembers = defel;
204                 }
205                 else if (strcmp(defel->defname, "validUntil") == 0)
206                 {
207                         if (dvalidUntil)
208                                 ereport(ERROR,
209                                                 (errcode(ERRCODE_SYNTAX_ERROR),
210                                                  errmsg("conflicting or redundant options")));
211                         dvalidUntil = defel;
212                 }
213                 else
214                         elog(ERROR, "option \"%s\" not recognized",
215                                  defel->defname);
216         }
217
218         if (dpassword)
219                 password = strVal(dpassword->arg);
220         if (dissuper)
221                 issuper = intVal(dissuper->arg) != 0;
222         if (dinherit)
223                 inherit = intVal(dinherit->arg) != 0;
224         if (dcreaterole)
225                 createrole = intVal(dcreaterole->arg) != 0;
226         if (dcreatedb)
227                 createdb = intVal(dcreatedb->arg) != 0;
228         if (dcanlogin)
229                 canlogin = intVal(dcanlogin->arg) != 0;
230         if (daddroleto)
231                 addroleto = (List *) daddroleto->arg;
232         if (drolemembers)
233                 rolemembers = (List *) drolemembers->arg;
234         if (dadminmembers)
235                 adminmembers = (List *) dadminmembers->arg;
236         if (dvalidUntil)
237                 validUntil = strVal(dvalidUntil->arg);
238
239         /* Check some permissions first */
240         if (issuper)
241         {
242                 if (!superuser())
243                         ereport(ERROR,
244                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
245                                          errmsg("must be superuser to create superusers")));
246         }
247         else
248         {
249                 if (!have_createrole_privilege())
250                         ereport(ERROR,
251                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
252                                          errmsg("permission denied to create role")));
253         }
254
255         if (strcmp(stmt->role, "public") == 0 ||
256                 strcmp(stmt->role, "none") == 0)
257                 ereport(ERROR,
258                                 (errcode(ERRCODE_RESERVED_NAME),
259                                  errmsg("role name \"%s\" is reserved",
260                                                 stmt->role)));
261
262         /*
263          * Check the pg_authid relation to be certain the role doesn't
264          * already exist.  Note we secure exclusive lock because
265          * we need to protect our eventual update of the flat auth file.
266          */
267         pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
268         pg_authid_dsc = RelationGetDescr(pg_authid_rel);
269
270         tuple = SearchSysCache(AUTHNAME,
271                                                    PointerGetDatum(stmt->role),
272                                                    0, 0, 0);
273         if (HeapTupleIsValid(tuple))
274                 ereport(ERROR,
275                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
276                                  errmsg("role \"%s\" already exists",
277                                                 stmt->role)));
278
279         /*
280          * Build a tuple to insert
281          */
282         MemSet(new_record, 0, sizeof(new_record));
283         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
284
285         new_record[Anum_pg_authid_rolname - 1] =
286                 DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
287
288         new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
289         new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
290         new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
291         new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
292         /* superuser gets catupdate right by default */
293         new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
294         new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
295
296         if (password)
297         {
298                 if (!encrypt_password || isMD5(password))
299                         new_record[Anum_pg_authid_rolpassword - 1] =
300                                 DirectFunctionCall1(textin, CStringGetDatum(password));
301                 else
302                 {
303                         if (!EncryptMD5(password, stmt->role, strlen(stmt->role),
304                                                         encrypted_password))
305                                 elog(ERROR, "password encryption failed");
306                         new_record[Anum_pg_authid_rolpassword - 1] =
307                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
308                 }
309         }
310         else
311                 new_record_nulls[Anum_pg_authid_rolpassword - 1] = 'n';
312
313         if (validUntil)
314                 new_record[Anum_pg_authid_rolvaliduntil - 1] =
315                         DirectFunctionCall3(timestamptz_in,
316                                                                 CStringGetDatum(validUntil),
317                                                                 ObjectIdGetDatum(InvalidOid),
318                                                                 Int32GetDatum(-1));
319
320         else
321                 new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = 'n';
322
323         new_record_nulls[Anum_pg_authid_rolconfig - 1] = 'n';
324
325         tuple = heap_formtuple(pg_authid_dsc, new_record, new_record_nulls);
326
327         /*
328          * Insert new record in the pg_authid table
329          */
330         roleid = simple_heap_insert(pg_authid_rel, tuple);
331         CatalogUpdateIndexes(pg_authid_rel, tuple);
332
333         /*
334          * Advance command counter so we can see new record; else tests
335          * in AddRoleMems may fail.
336          */
337         if (addroleto || adminmembers || rolemembers)
338                 CommandCounterIncrement();
339
340         /*
341          * Add the new role to the specified existing roles.
342          */
343         foreach(item, addroleto)
344         {
345                 char   *oldrolename = strVal(lfirst(item));
346                 Oid             oldroleid = get_roleid_checked(oldrolename);
347
348                 AddRoleMems(oldrolename, oldroleid,
349                                         list_make1(makeString(stmt->role)),
350                                         list_make1_oid(roleid),
351                                         GetUserId(), false);
352         }
353
354         /*
355          * Add the specified members to this new role. adminmembers get the
356          * admin option, rolemembers don't.
357          */
358         AddRoleMems(stmt->role, roleid,
359                                 adminmembers, roleNamesToIds(adminmembers),
360                                 GetUserId(), true);
361         AddRoleMems(stmt->role, roleid,
362                                 rolemembers, roleNamesToIds(rolemembers),
363                                 GetUserId(), false);
364
365         /*
366          * Now we can clean up; but keep lock until commit (to avoid possible
367          * deadlock when commit code tries to acquire lock).
368          */
369         heap_close(pg_authid_rel, NoLock);
370
371         /*
372          * Set flag to update flat auth file at commit.
373          */
374         auth_file_update_needed();
375 }
376
377
378 /*
379  * ALTER ROLE
380  *
381  * Note: the rolemembers option accepted here is intended to support the
382  * backwards-compatible ALTER GROUP syntax.  Although it will work to say
383  * "ALTER ROLE role ROLE rolenames", we don't document it.
384  */
385 void
386 AlterRole(AlterRoleStmt *stmt)
387 {
388         Datum           new_record[Natts_pg_authid];
389         char            new_record_nulls[Natts_pg_authid];
390         char            new_record_repl[Natts_pg_authid];
391         Relation        pg_authid_rel;
392         TupleDesc       pg_authid_dsc;
393         HeapTuple       tuple,
394                                 new_tuple;
395         ListCell   *option;
396         char       *password = NULL;            /* user password */
397         bool            encrypt_password = Password_encryption; /* encrypt password? */
398         char            encrypted_password[MD5_PASSWD_LEN + 1];
399         int                     issuper = -1;                   /* Make the user a superuser? */
400         int                     inherit = -1;                   /* Auto inherit privileges? */
401         int                     createrole = -1;                /* Can this user create roles? */
402         int                     createdb = -1;                  /* Can the user create databases? */
403         int                     canlogin = -1;                  /* Can this user login? */
404         List       *rolemembers = NIL;          /* roles to be added/removed */
405         char       *validUntil = NULL;          /* time the login is valid until */
406         DefElem    *dpassword = NULL;
407         DefElem    *dissuper = NULL;
408         DefElem    *dinherit = NULL;
409         DefElem    *dcreaterole = NULL;
410         DefElem    *dcreatedb = NULL;
411         DefElem    *dcanlogin = NULL;
412         DefElem    *drolemembers = NULL;
413         DefElem    *dvalidUntil = NULL;
414         Oid                     roleid;
415
416         /* Extract options from the statement node tree */
417         foreach(option, stmt->options)
418         {
419                 DefElem    *defel = (DefElem *) lfirst(option);
420
421                 if (strcmp(defel->defname, "password") == 0 ||
422                         strcmp(defel->defname, "encryptedPassword") == 0 ||
423                         strcmp(defel->defname, "unencryptedPassword") == 0)
424                 {
425                         if (dpassword)
426                                 ereport(ERROR,
427                                                 (errcode(ERRCODE_SYNTAX_ERROR),
428                                                  errmsg("conflicting or redundant options")));
429                         dpassword = defel;
430                         if (strcmp(defel->defname, "encryptedPassword") == 0)
431                                 encrypt_password = true;
432                         else if (strcmp(defel->defname, "unencryptedPassword") == 0)
433                                 encrypt_password = false;
434                 }
435                 else if (strcmp(defel->defname, "superuser") == 0)
436                 {
437                         if (dissuper)
438                                 ereport(ERROR,
439                                                 (errcode(ERRCODE_SYNTAX_ERROR),
440                                                  errmsg("conflicting or redundant options")));
441                         dissuper = defel;
442                 }
443                 else if (strcmp(defel->defname, "inherit") == 0)
444                 {
445                         if (dinherit)
446                                 ereport(ERROR,
447                                                 (errcode(ERRCODE_SYNTAX_ERROR),
448                                                  errmsg("conflicting or redundant options")));
449                         dinherit = defel;
450                 }
451                 else if (strcmp(defel->defname, "createrole") == 0)
452                 {
453                         if (dcreaterole)
454                                 ereport(ERROR,
455                                                 (errcode(ERRCODE_SYNTAX_ERROR),
456                                                  errmsg("conflicting or redundant options")));
457                         dcreaterole = defel;
458                 }
459                 else if (strcmp(defel->defname, "createdb") == 0)
460                 {
461                         if (dcreatedb)
462                                 ereport(ERROR,
463                                                 (errcode(ERRCODE_SYNTAX_ERROR),
464                                                  errmsg("conflicting or redundant options")));
465                         dcreatedb = defel;
466                 }
467                 else if (strcmp(defel->defname, "canlogin") == 0)
468                 {
469                         if (dcanlogin)
470                                 ereport(ERROR,
471                                                 (errcode(ERRCODE_SYNTAX_ERROR),
472                                                  errmsg("conflicting or redundant options")));
473                         dcanlogin = defel;
474                 }
475                 else if (strcmp(defel->defname, "rolemembers") == 0 &&
476                                  stmt->action != 0)
477                 {
478                         if (drolemembers)
479                                 ereport(ERROR,
480                                                 (errcode(ERRCODE_SYNTAX_ERROR),
481                                                  errmsg("conflicting or redundant options")));
482                         drolemembers = defel;
483                 }
484                 else if (strcmp(defel->defname, "validUntil") == 0)
485                 {
486                         if (dvalidUntil)
487                                 ereport(ERROR,
488                                                 (errcode(ERRCODE_SYNTAX_ERROR),
489                                                  errmsg("conflicting or redundant options")));
490                         dvalidUntil = defel;
491                 }
492                 else
493                         elog(ERROR, "option \"%s\" not recognized",
494                                  defel->defname);
495         }
496
497         if (dpassword)
498                 password = strVal(dpassword->arg);
499         if (dissuper)
500                 issuper = intVal(dissuper->arg);
501         if (dinherit)
502                 inherit = intVal(dinherit->arg);
503         if (dcreaterole)
504                 createrole = intVal(dcreaterole->arg);
505         if (dcreatedb)
506                 createdb = intVal(dcreatedb->arg);
507         if (dcanlogin)
508                 canlogin = intVal(dcanlogin->arg);
509         if (drolemembers)
510                 rolemembers = (List *) drolemembers->arg;
511         if (dvalidUntil)
512                 validUntil = strVal(dvalidUntil->arg);
513
514         /*
515          * Scan the pg_authid relation to be certain the user exists. Note we
516          * secure exclusive lock to protect our update of the flat auth file.
517          */
518         pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
519         pg_authid_dsc = RelationGetDescr(pg_authid_rel);
520
521         tuple = SearchSysCache(AUTHNAME,
522                                                    PointerGetDatum(stmt->role),
523                                                    0, 0, 0);
524         if (!HeapTupleIsValid(tuple))
525                 ereport(ERROR,
526                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
527                                  errmsg("role \"%s\" does not exist", stmt->role)));
528
529         roleid = HeapTupleGetOid(tuple);
530
531         /*
532          * To mess with a superuser you gotta be superuser; else you need
533          * createrole, or just want to change your own password
534          */
535         if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
536         {
537                 if (!superuser())
538                         ereport(ERROR,
539                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
540                                          errmsg("must be superuser to alter superusers")));
541         }
542         else if (!have_createrole_privilege())
543         {
544                 if (!(inherit < 0 &&
545                           createrole < 0 &&
546                           createdb < 0 &&
547                           canlogin < 0 &&
548                           !rolemembers &&
549                           !validUntil &&
550                           password &&
551                           roleid == GetUserId()))
552                         ereport(ERROR,
553                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
554                                          errmsg("permission denied")));
555         }
556
557         /*
558          * Build an updated tuple, perusing the information just obtained
559          */
560         MemSet(new_record, 0, sizeof(new_record));
561         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
562         MemSet(new_record_repl, ' ', sizeof(new_record_repl));
563
564         /*
565          * issuper/createrole/catupdate/etc
566          *
567          * XXX It's rather unclear how to handle catupdate.  It's probably best to
568          * keep it equal to the superuser status, otherwise you could end up
569          * with a situation where no existing superuser can alter the
570          * catalogs, including pg_authid!
571          */
572         if (issuper >= 0)
573         {
574                 new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
575                 new_record_repl[Anum_pg_authid_rolsuper - 1] = 'r';
576
577                 new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
578                 new_record_repl[Anum_pg_authid_rolcatupdate - 1] = 'r';
579         }
580
581         if (inherit >= 0)
582         {
583                 new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
584                 new_record_repl[Anum_pg_authid_rolinherit - 1] = 'r';
585         }
586
587         if (createrole >= 0)
588         {
589                 new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
590                 new_record_repl[Anum_pg_authid_rolcreaterole - 1] = 'r';
591         }
592
593         if (createdb >= 0)
594         {
595                 new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
596                 new_record_repl[Anum_pg_authid_rolcreatedb - 1] = 'r';
597         }
598
599         if (canlogin >= 0)
600         {
601                 new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
602                 new_record_repl[Anum_pg_authid_rolcanlogin - 1] = 'r';
603         }
604
605         /* password */
606         if (password)
607         {
608                 if (!encrypt_password || isMD5(password))
609                         new_record[Anum_pg_authid_rolpassword - 1] =
610                                 DirectFunctionCall1(textin, CStringGetDatum(password));
611                 else
612                 {
613                         if (!EncryptMD5(password, stmt->role, strlen(stmt->role),
614                                                         encrypted_password))
615                                 elog(ERROR, "password encryption failed");
616                         new_record[Anum_pg_authid_rolpassword - 1] =
617                                 DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
618                 }
619                 new_record_repl[Anum_pg_authid_rolpassword - 1] = 'r';
620         }
621
622         /* valid until */
623         if (validUntil)
624         {
625                 new_record[Anum_pg_authid_rolvaliduntil - 1] =
626                         DirectFunctionCall3(timestamptz_in,
627                                                                 CStringGetDatum(validUntil),
628                                                                 ObjectIdGetDatum(InvalidOid),
629                                                                 Int32GetDatum(-1));
630                 new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = 'r';
631         }
632
633         new_tuple = heap_modifytuple(tuple, pg_authid_dsc, new_record,
634                                                                  new_record_nulls, new_record_repl);
635         simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
636
637         /* Update indexes */
638         CatalogUpdateIndexes(pg_authid_rel, new_tuple);
639
640         ReleaseSysCache(tuple);
641         heap_freetuple(new_tuple);
642
643         /*
644          * Advance command counter so we can see new record; else tests
645          * in AddRoleMems may fail.
646          */
647         if (rolemembers)
648                 CommandCounterIncrement();
649
650         if (stmt->action == +1)         /* add members to role */
651                 AddRoleMems(stmt->role, roleid,
652                                         rolemembers, roleNamesToIds(rolemembers),
653                                         GetUserId(), false);
654         else if (stmt->action == -1)    /* drop members from role */
655                 DelRoleMems(stmt->role, roleid,
656                                         rolemembers, roleNamesToIds(rolemembers),
657                                         false);
658
659         /*
660          * Now we can clean up; but keep lock until commit (to avoid possible
661          * deadlock when commit code tries to acquire lock).
662          */
663         heap_close(pg_authid_rel, NoLock);
664
665         /*
666          * Set flag to update flat auth file at commit.
667          */
668         auth_file_update_needed();
669 }
670
671
672 /*
673  * ALTER ROLE ... SET
674  */
675 void
676 AlterRoleSet(AlterRoleSetStmt *stmt)
677 {
678         char       *valuestr;
679         HeapTuple       oldtuple,
680                                 newtuple;
681         Relation        rel;
682         Datum           repl_val[Natts_pg_authid];
683         char            repl_null[Natts_pg_authid];
684         char            repl_repl[Natts_pg_authid];
685         int                     i;
686
687         valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
688
689         /*
690          * RowExclusiveLock is sufficient, because we don't need to update the
691          * flat auth file.
692          */
693         rel = heap_open(AuthIdRelationId, RowExclusiveLock);
694         oldtuple = SearchSysCache(AUTHNAME,
695                                                           PointerGetDatum(stmt->role),
696                                                           0, 0, 0);
697         if (!HeapTupleIsValid(oldtuple))
698                 ereport(ERROR,
699                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
700                                  errmsg("role \"%s\" does not exist", stmt->role)));
701
702         /*
703          * To mess with a superuser you gotta be superuser; else you need
704          * createrole, or just want to change your own settings
705          */
706         if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
707         {
708                 if (!superuser())
709                         ereport(ERROR,
710                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
711                                          errmsg("must be superuser to alter superusers")));
712         }
713         else
714         {
715                 if (!have_createrole_privilege() &&
716                         HeapTupleGetOid(oldtuple) != GetUserId())
717                         ereport(ERROR,
718                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
719                                          errmsg("permission denied")));
720         }
721
722         for (i = 0; i < Natts_pg_authid; i++)
723                 repl_repl[i] = ' ';
724
725         repl_repl[Anum_pg_authid_rolconfig - 1] = 'r';
726         if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
727         {
728                 /* RESET ALL */
729                 repl_null[Anum_pg_authid_rolconfig - 1] = 'n';
730         }
731         else
732         {
733                 Datum           datum;
734                 bool            isnull;
735                 ArrayType  *array;
736
737                 repl_null[Anum_pg_authid_rolconfig - 1] = ' ';
738
739                 datum = SysCacheGetAttr(AUTHNAME, oldtuple,
740                                                                 Anum_pg_authid_rolconfig, &isnull);
741
742                 array = isnull ? NULL : DatumGetArrayTypeP(datum);
743
744                 if (valuestr)
745                         array = GUCArrayAdd(array, stmt->variable, valuestr);
746                 else
747                         array = GUCArrayDelete(array, stmt->variable);
748
749                 if (array)
750                         repl_val[Anum_pg_authid_rolconfig - 1] = PointerGetDatum(array);
751                 else
752                         repl_null[Anum_pg_authid_rolconfig - 1] = 'n';
753         }
754
755         newtuple = heap_modifytuple(oldtuple, RelationGetDescr(rel),
756                                                                 repl_val, repl_null, repl_repl);
757
758         simple_heap_update(rel, &oldtuple->t_self, newtuple);
759         CatalogUpdateIndexes(rel, newtuple);
760
761         ReleaseSysCache(oldtuple);
762         heap_close(rel, RowExclusiveLock);
763 }
764
765
766 /*
767  * DROP ROLE
768  */
769 void
770 DropRole(DropRoleStmt *stmt)
771 {
772         Relation        pg_authid_rel, pg_auth_members_rel;
773         ListCell   *item;
774
775         if (!have_createrole_privilege())
776                 ereport(ERROR,
777                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
778                                  errmsg("permission denied to drop role")));
779
780         /*
781          * Scan the pg_authid relation to find the Oid of the role(s) to be
782          * deleted.  Note we secure exclusive lock on pg_authid, because we
783          * need to protect our update of the flat auth file.  A regular
784          * writer's lock on pg_auth_members is sufficient though.
785          */
786         pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
787         pg_auth_members_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
788
789         foreach(item, stmt->roles)
790         {
791                 const char *role = strVal(lfirst(item));
792                 HeapTuple       tuple,
793                                         tmp_tuple;
794                 ScanKeyData     scankey;
795                 char       *detail;
796                 SysScanDesc sscan;
797                 Oid                     roleid;
798
799                 tuple = SearchSysCache(AUTHNAME,
800                                                            PointerGetDatum(role),
801                                                            0, 0, 0);
802                 if (!HeapTupleIsValid(tuple))
803                         ereport(ERROR,
804                                         (errcode(ERRCODE_UNDEFINED_OBJECT),
805                                          errmsg("role \"%s\" does not exist", role)));
806
807                 roleid = HeapTupleGetOid(tuple);
808
809                 if (roleid == GetUserId())
810                         ereport(ERROR,
811                                         (errcode(ERRCODE_OBJECT_IN_USE),
812                                          errmsg("current user cannot be dropped")));
813                 if (roleid == GetOuterUserId())
814                         ereport(ERROR,
815                                         (errcode(ERRCODE_OBJECT_IN_USE),
816                                          errmsg("current user cannot be dropped")));
817                 if (roleid == GetSessionUserId())
818                         ereport(ERROR,
819                                         (errcode(ERRCODE_OBJECT_IN_USE),
820                                          errmsg("session user cannot be dropped")));
821
822                 /*
823                  * For safety's sake, we allow createrole holders to drop ordinary
824                  * roles but not superuser roles.  This is mainly to avoid the
825                  * scenario where you accidentally drop the last superuser.
826                  */
827                 if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
828                         !superuser())
829                         ereport(ERROR,
830                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
831                                          errmsg("must be superuser to drop superusers")));
832
833                 /*
834                  * Lock the role, so nobody can add dependencies to her while we drop
835                  * her.  We keep the lock until the end of transaction.
836                  */
837                 LockSharedObject(AuthIdRelationId, roleid, 0, AccessExclusiveLock);
838
839                 /* Check for pg_shdepend entries depending on this role */
840                 if ((detail = checkSharedDependencies(AuthIdRelationId, roleid)) != NULL)
841                         ereport(ERROR,
842                                         (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
843                                          errmsg("role \"%s\" cannot be dropped because some objects depend on it",
844                                                         role),
845                                          errdetail("%s", detail)));
846
847                 /*
848                  * Remove the role from the pg_authid table
849                  */
850                 simple_heap_delete(pg_authid_rel, &tuple->t_self);
851
852                 ReleaseSysCache(tuple);
853
854                 /*
855                  * Remove role from the pg_auth_members table.  We have to remove
856                  * all tuples that show it as either a role or a member.
857                  *
858                  * XXX what about grantor entries?  Maybe we should do one heap scan.
859                  */
860                 ScanKeyInit(&scankey,
861                                         Anum_pg_auth_members_roleid,
862                                         BTEqualStrategyNumber, F_OIDEQ,
863                                         ObjectIdGetDatum(roleid));
864
865                 sscan = systable_beginscan(pg_auth_members_rel, AuthMemRoleMemIndexId,
866                                                                    true, SnapshotNow, 1, &scankey);
867
868                 while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
869                 {
870                         simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self);
871                 }
872
873                 systable_endscan(sscan);
874
875                 ScanKeyInit(&scankey,
876                                         Anum_pg_auth_members_member,
877                                         BTEqualStrategyNumber, F_OIDEQ,
878                                         ObjectIdGetDatum(roleid));
879
880                 sscan = systable_beginscan(pg_auth_members_rel, AuthMemMemRoleIndexId,
881                                                                    true, SnapshotNow, 1, &scankey);
882
883                 while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
884                 {
885                         simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self);
886                 }
887
888                 systable_endscan(sscan);
889
890                 /*
891                  * Advance command counter so that later iterations of this loop
892                  * will see the changes already made.  This is essential if, for
893                  * example, we are trying to drop both a role and one of its
894                  * direct members --- we'll get an error if we try to delete the
895                  * linking pg_auth_members tuple twice.  (We do not need a CCI
896                  * between the two delete loops above, because it's not allowed
897                  * for a role to directly contain itself.)
898                  */
899                 CommandCounterIncrement();
900         }
901
902         /*
903          * Now we can clean up; but keep locks until commit (to avoid possible
904          * deadlock when commit code tries to acquire lock).
905          */
906         heap_close(pg_auth_members_rel, NoLock);
907         heap_close(pg_authid_rel, NoLock);
908
909         /*
910          * Set flag to update flat auth file at commit.
911          */
912         auth_file_update_needed();
913 }
914
915 /*
916  * Rename role
917  */
918 void
919 RenameRole(const char *oldname, const char *newname)
920 {
921         HeapTuple       oldtuple,
922                                 newtuple;
923         TupleDesc       dsc;
924         Relation        rel;
925         Datum           datum;
926         bool            isnull;
927         Datum           repl_val[Natts_pg_authid];
928         char            repl_null[Natts_pg_authid];
929         char            repl_repl[Natts_pg_authid];
930         int                     i;
931         Oid                     roleid;
932
933         /* ExclusiveLock because we need to update the flat auth file */
934         rel = heap_open(AuthIdRelationId, ExclusiveLock);
935         dsc = RelationGetDescr(rel);
936
937         oldtuple = SearchSysCache(AUTHNAME,
938                                                           CStringGetDatum(oldname),
939                                                           0, 0, 0);
940         if (!HeapTupleIsValid(oldtuple))
941                 ereport(ERROR,
942                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
943                                  errmsg("role \"%s\" does not exist", oldname)));
944
945         /*
946          * XXX Client applications probably store the session user somewhere,
947          * so renaming it could cause confusion.  On the other hand, there may
948          * not be an actual problem besides a little confusion, so think about
949          * this and decide.  Same for SET ROLE ... we don't restrict renaming
950          * the current effective userid, though.
951          */
952
953         roleid = HeapTupleGetOid(oldtuple);
954
955         if (roleid == GetSessionUserId())
956                 ereport(ERROR,
957                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
958                                  errmsg("session user may not be renamed")));
959         if (roleid == GetOuterUserId())
960                 ereport(ERROR,
961                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
962                                  errmsg("current user may not be renamed")));
963
964         /* make sure the new name doesn't exist */
965         if (SearchSysCacheExists(AUTHNAME,
966                                                          CStringGetDatum(newname),
967                                                          0, 0, 0))
968                 ereport(ERROR,
969                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
970                                  errmsg("role \"%s\" already exists", newname)));
971
972         if (strcmp(newname, "public") == 0 ||
973                 strcmp(newname, "none") == 0)
974                 ereport(ERROR,
975                                 (errcode(ERRCODE_RESERVED_NAME),
976                                  errmsg("role name \"%s\" is reserved",
977                                                 newname)));
978
979         /*
980          * createrole is enough privilege unless you want to mess with a superuser
981          */
982         if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
983         {
984                 if (!superuser())
985                         ereport(ERROR,
986                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
987                                          errmsg("must be superuser to rename superusers")));
988         }
989         else
990         {
991                 if (!have_createrole_privilege())
992                         ereport(ERROR,
993                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
994                                          errmsg("permission denied to rename role")));
995         }
996
997         /* OK, construct the modified tuple */
998         for (i = 0; i < Natts_pg_authid; i++)
999                 repl_repl[i] = ' ';
1000
1001         repl_repl[Anum_pg_authid_rolname - 1] = 'r';
1002         repl_val[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein,
1003                                                                                            CStringGetDatum(newname));
1004         repl_null[Anum_pg_authid_rolname - 1] = ' ';
1005
1006         datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
1007
1008         if (!isnull && isMD5(DatumGetCString(DirectFunctionCall1(textout, datum))))
1009         {
1010                 /* MD5 uses the username as salt, so just clear it on a rename */
1011                 repl_repl[Anum_pg_authid_rolpassword - 1] = 'r';
1012                 repl_null[Anum_pg_authid_rolpassword - 1] = 'n';
1013
1014                 ereport(NOTICE,
1015                                 (errmsg("MD5 password cleared because of role rename")));
1016         }
1017
1018         newtuple = heap_modifytuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
1019         simple_heap_update(rel, &oldtuple->t_self, newtuple);
1020
1021         CatalogUpdateIndexes(rel, newtuple);
1022
1023         ReleaseSysCache(oldtuple);
1024         heap_close(rel, NoLock);
1025
1026         /*
1027          * Set flag to update flat auth file at commit.
1028          */
1029         auth_file_update_needed();
1030 }
1031
1032 /*
1033  * GrantRoleStmt
1034  *
1035  * Grant/Revoke roles to/from roles
1036  */
1037 void
1038 GrantRole(GrantRoleStmt *stmt)
1039 {
1040         Relation        pg_authid_rel;
1041         Oid                     grantor;
1042         List       *grantee_ids;
1043         ListCell   *item;
1044
1045         if (stmt->grantor)
1046                 grantor = get_roleid_checked(stmt->grantor);
1047         else
1048                 grantor = GetUserId();
1049
1050         grantee_ids = roleNamesToIds(stmt->grantee_roles);
1051
1052         /*
1053          * Even though this operation doesn't change pg_authid, we must
1054          * secure exclusive lock on it to protect our update of the flat
1055          * auth file.
1056          */
1057         pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
1058
1059         /*
1060          * Step through all of the granted roles and add/remove
1061          * entries for the grantees, or, if admin_opt is set, then
1062          * just add/remove the admin option.
1063          *
1064          * Note: Permissions checking is done by AddRoleMems/DelRoleMems
1065          */
1066         foreach(item, stmt->granted_roles)
1067         {
1068                 char   *rolename = strVal(lfirst(item));
1069                 Oid             roleid = get_roleid_checked(rolename);
1070
1071                 if (stmt->is_grant)
1072                         AddRoleMems(rolename, roleid,
1073                                                 stmt->grantee_roles, grantee_ids,
1074                                                 grantor, stmt->admin_opt);
1075                 else
1076                         DelRoleMems(rolename, roleid,
1077                                                 stmt->grantee_roles, grantee_ids,
1078                                                 stmt->admin_opt);
1079         }
1080
1081         heap_close(pg_authid_rel, NoLock);
1082
1083         /*
1084          * Set flag to update flat auth file at commit.
1085          */
1086         auth_file_update_needed();
1087 }
1088
1089 /*
1090  * roleNamesToIds
1091  *
1092  * Given a list of role names (as String nodes), generate a list of role OIDs
1093  * in the same order.
1094  */
1095 static List *
1096 roleNamesToIds(List *memberNames)
1097 {
1098         List       *result = NIL;
1099         ListCell   *l;
1100
1101         foreach(l, memberNames)
1102         {
1103                 char   *rolename = strVal(lfirst(l));
1104                 Oid             roleid = get_roleid_checked(rolename);
1105
1106                 result = lappend_oid(result, roleid);
1107         }
1108         return result;
1109 }
1110
1111 /*
1112  * AddRoleMems -- Add given members to the specified role
1113  *
1114  * rolename: name of role to add to (used only for error messages)
1115  * roleid: OID of role to add to
1116  * memberNames: list of names of roles to add (used only for error messages)
1117  * memberIds: OIDs of roles to add
1118  * grantorId: who is granting the membership
1119  * admin_opt: granting admin option?
1120  *
1121  * Note: caller is responsible for holding ExclusiveLock on pg_authid,
1122  * and for calling auth_file_update_needed().
1123  */
1124 static void
1125 AddRoleMems(const char *rolename, Oid roleid,
1126                         List *memberNames, List *memberIds,
1127                         Oid grantorId, bool admin_opt)
1128 {
1129         Relation        pg_authmem_rel;
1130         TupleDesc       pg_authmem_dsc;
1131         ListCell        *nameitem;
1132         ListCell        *iditem;
1133
1134         Assert(list_length(memberNames) == list_length(memberIds));
1135
1136         /* Skip permission check if nothing to do */
1137         if (!memberIds)
1138                 return;
1139
1140         /*
1141          * Check permissions: must have createrole or admin option on the
1142          * role to be changed.  To mess with a superuser role, you gotta
1143          * be superuser.
1144          */
1145         if (superuser_arg(roleid))
1146         {
1147                 if (!superuser())
1148                         ereport(ERROR,
1149                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1150                                          errmsg("must be superuser to alter superusers")));
1151         }
1152         else
1153         {
1154                 if (!have_createrole_privilege() &&
1155                         !is_admin_of_role(grantorId, roleid))
1156                         ereport(ERROR,
1157                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1158                                          errmsg("must have admin option on role \"%s\"",
1159                                                         rolename)));
1160         }
1161
1162         /* XXX not sure about this check */
1163         if (grantorId != GetUserId() && !superuser())
1164                 ereport(ERROR,
1165                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1166                                  errmsg("must be superuser to set grantor ID")));
1167
1168         /* We need only regular writer's lock on pg_auth_members */
1169         pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
1170         pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
1171
1172         forboth(nameitem, memberNames, iditem, memberIds)
1173         {
1174                 const char *membername = strVal(lfirst(nameitem));
1175                 Oid                     memberid = lfirst_oid(iditem);
1176                 HeapTuple       authmem_tuple;
1177                 HeapTuple       tuple;
1178                 Datum   new_record[Natts_pg_auth_members];
1179                 char    new_record_nulls[Natts_pg_auth_members];
1180                 char    new_record_repl[Natts_pg_auth_members];
1181
1182                 /*
1183                  * Refuse creation of membership loops, including the trivial case
1184                  * where a role is made a member of itself.  We do this by checking
1185                  * to see if the target role is already a member of the proposed
1186                  * member role.
1187                  */
1188                 if (is_member_of_role(roleid, memberid))
1189                         ereport(ERROR,
1190                                         (errcode(ERRCODE_INVALID_GRANT_OPERATION),
1191                                         (errmsg("role \"%s\" is a member of role \"%s\"",
1192                                                         rolename, membername))));
1193
1194                 /*
1195                  * Check if entry for this role/member already exists;
1196                  * if so, give warning unless we are adding admin option.
1197                  */
1198                 authmem_tuple = SearchSysCache(AUTHMEMROLEMEM,
1199                                                                            ObjectIdGetDatum(roleid),
1200                                                                            ObjectIdGetDatum(memberid),
1201                                                                            0, 0);
1202                 if (HeapTupleIsValid(authmem_tuple) &&
1203                         (!admin_opt || 
1204                          ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
1205                 {
1206                         ereport(NOTICE,
1207                                         (errmsg("role \"%s\" is already a member of role \"%s\"",
1208                                                         membername, rolename)));
1209                         ReleaseSysCache(authmem_tuple);
1210                         continue;
1211                 }
1212
1213                 /* Build a tuple to insert or update */
1214                 MemSet(new_record, 0, sizeof(new_record));
1215                 MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1216                 MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1217
1218                 new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
1219                 new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
1220                 new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
1221                 new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
1222
1223                 if (HeapTupleIsValid(authmem_tuple))
1224                 {
1225                         new_record_repl[Anum_pg_auth_members_grantor - 1] = 'r';
1226                         new_record_repl[Anum_pg_auth_members_admin_option - 1] = 'r';
1227                         tuple = heap_modifytuple(authmem_tuple, pg_authmem_dsc,
1228                                                                          new_record,
1229                                                                          new_record_nulls, new_record_repl);
1230                         simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);
1231                         CatalogUpdateIndexes(pg_authmem_rel, tuple);
1232                         ReleaseSysCache(authmem_tuple);
1233                 }
1234                 else
1235                 {
1236                         tuple = heap_formtuple(pg_authmem_dsc,
1237                                                                    new_record, new_record_nulls);
1238                         simple_heap_insert(pg_authmem_rel, tuple);
1239                         CatalogUpdateIndexes(pg_authmem_rel, tuple);
1240                 }
1241
1242                 /* CCI after each change, in case there are duplicates in list */
1243                 CommandCounterIncrement();
1244         }
1245
1246         /*
1247          * Now we can clean up; but keep lock until commit (to avoid possible
1248          * deadlock when commit code tries to acquire lock).
1249          */
1250         heap_close(pg_authmem_rel, NoLock);
1251 }
1252
1253 /*
1254  * DelRoleMems -- Remove given members from the specified role
1255  *
1256  * rolename: name of role to del from (used only for error messages)
1257  * roleid: OID of role to del from
1258  * memberNames: list of names of roles to del (used only for error messages)
1259  * memberIds: OIDs of roles to del
1260  * admin_opt: remove admin option only?
1261  *
1262  * Note: caller is responsible for holding ExclusiveLock on pg_authid,
1263  * and for calling auth_file_update_needed().
1264  */
1265 static void
1266 DelRoleMems(const char *rolename, Oid roleid,
1267                         List *memberNames, List *memberIds,
1268                         bool admin_opt)
1269 {
1270         Relation        pg_authmem_rel;
1271         TupleDesc       pg_authmem_dsc;
1272         ListCell        *nameitem;
1273         ListCell        *iditem;
1274
1275         Assert(list_length(memberNames) == list_length(memberIds));
1276
1277         /* Skip permission check if nothing to do */
1278         if (!memberIds)
1279                 return;
1280
1281         /*
1282          * Check permissions: must have createrole or admin option on the
1283          * role to be changed.  To mess with a superuser role, you gotta
1284          * be superuser.
1285          */
1286         if (superuser_arg(roleid))
1287         {
1288                 if (!superuser())
1289                         ereport(ERROR,
1290                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1291                                          errmsg("must be superuser to alter superusers")));
1292         }
1293         else
1294         {
1295                 if (!have_createrole_privilege() &&
1296                         !is_admin_of_role(GetUserId(), roleid))
1297                         ereport(ERROR,
1298                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1299                                          errmsg("must have admin option on role \"%s\"",
1300                                                         rolename)));
1301         }
1302
1303         /* We need only regular writer's lock on pg_auth_members */
1304         pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
1305         pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
1306
1307         forboth(nameitem, memberNames, iditem, memberIds)
1308         {
1309                 const char *membername = strVal(lfirst(nameitem));
1310                 Oid                     memberid = lfirst_oid(iditem);
1311                 HeapTuple       authmem_tuple;
1312
1313                 /*
1314                  * Find entry for this role/member
1315                  */
1316                 authmem_tuple = SearchSysCache(AUTHMEMROLEMEM,
1317                                                                            ObjectIdGetDatum(roleid),
1318                                                                            ObjectIdGetDatum(memberid),
1319                                                                            0, 0);
1320                 if (!HeapTupleIsValid(authmem_tuple))
1321                 {
1322                         ereport(WARNING,
1323                                         (errmsg("role \"%s\" is not a member of role \"%s\"",
1324                                                         membername, rolename)));
1325                         continue;
1326                 }
1327
1328                 if (!admin_opt)
1329                 {
1330                         /* Remove the entry altogether */
1331                         simple_heap_delete(pg_authmem_rel, &authmem_tuple->t_self);
1332                 }
1333                 else
1334                 {
1335                         /* Just turn off the admin option */
1336                         HeapTuple       tuple;
1337                         Datum   new_record[Natts_pg_auth_members];
1338                         char    new_record_nulls[Natts_pg_auth_members];
1339                         char    new_record_repl[Natts_pg_auth_members];
1340
1341                         /* Build a tuple to update with */
1342                         MemSet(new_record, 0, sizeof(new_record));
1343                         MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
1344                         MemSet(new_record_repl, ' ', sizeof(new_record_repl));
1345
1346                         new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
1347                         new_record_repl[Anum_pg_auth_members_admin_option - 1] = 'r';
1348
1349                         tuple = heap_modifytuple(authmem_tuple, pg_authmem_dsc,
1350                                                                          new_record,
1351                                                                          new_record_nulls, new_record_repl);
1352                         simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);
1353                         CatalogUpdateIndexes(pg_authmem_rel, tuple);
1354                 }
1355
1356                 ReleaseSysCache(authmem_tuple);
1357
1358                 /* CCI after each change, in case there are duplicates in list */
1359                 CommandCounterIncrement();
1360         }
1361
1362         /*
1363          * Now we can clean up; but keep lock until commit (to avoid possible
1364          * deadlock when commit code tries to acquire lock).
1365          */
1366         heap_close(pg_authmem_rel, NoLock);
1367 }