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