1 /*-------------------------------------------------------------------------
4 * Basic access control list data structures manipulation routines.
6 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.57 2001/01/24 19:43:12 momjian Exp $
13 *-------------------------------------------------------------------------
19 #include "access/heapam.h"
20 #include "catalog/catalog.h"
21 #include "catalog/pg_shadow.h"
22 #include "catalog/pg_type.h"
23 #include "lib/stringinfo.h"
24 #include "miscadmin.h"
25 #include "utils/acl.h"
26 #include "utils/builtins.h"
27 #include "utils/memutils.h"
28 #include "utils/syscache.h"
30 static char *getid(char *s, char *n);
31 static bool aclitemeq(AclItem *a1, AclItem *a2);
32 static bool aclitemgt(AclItem *a1, AclItem *a2);
33 static char *aclparse(char *s, AclItem *aip, unsigned *modechg);
35 #define ACL_IDTYPE_GID_KEYWORD "group"
36 #define ACL_IDTYPE_UID_KEYWORD "user"
41 * Consumes the first alphanumeric string (identifier) found in string
42 * 's', ignoring any leading white space. If it finds a double quote
43 * it returns the word inside the quotes.
46 * the string position in 's' that points to the next non-space character
47 * in 's', after any quotes. Also:
48 * - loads the identifier into 'name'. (If no identifier is found, 'name'
49 * contains an empty string.) name must be NAMEDATALEN bytes.
52 getid(char *s, char *n)
60 while (isspace((unsigned char) *s))
70 isalnum((unsigned char) *s) || *s == '_' || in_quotes;
73 if (in_quotes && *s == '"')
79 if (len >= NAMEDATALEN)
80 elog(ERROR, "getid: identifier must be <%d characters",
85 while (isspace((unsigned char) *s))
92 * Consumes and parses an ACL specification of the form:
93 * [group|user] [A-Za-z0-9]*[+-=][rwaR]*
94 * from string 's', ignoring any leading white space or white space
95 * between the optional id type keyword (group|user) and the actual
98 * This routine is called by the parser as well as aclitemin(), hence
99 * the added generality.
102 * the string position in 's' immediately following the ACL
103 * specification. Also:
104 * - loads the structure pointed to by 'aip' with the appropriate
105 * UID/GID, id type identifier and mode type values.
106 * - loads 'modechg' with the mode change flag.
109 aclparse(char *s, AclItem *aip, unsigned *modechg)
112 char name[NAMEDATALEN];
114 Assert(s && aip && modechg);
116 #ifdef ACLDEBUG_TRACE
117 printf("aclparse: input = '%s'\n", s);
119 aip->ai_idtype = ACL_IDTYPE_UID;
121 if (*s != ACL_MODECHG_ADD_CHR &&
122 *s != ACL_MODECHG_DEL_CHR &&
123 *s != ACL_MODECHG_EQL_CHR)
125 /* we just read a keyword, not a name */
126 if (!strcmp(name, ACL_IDTYPE_GID_KEYWORD))
127 aip->ai_idtype = ACL_IDTYPE_GID;
128 else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD))
129 elog(ERROR, "aclparse: bad keyword, must be [group|user]");
130 s = getid(s, name); /* move s to the name beyond the keyword */
132 elog(ERROR, "aclparse: a name must follow the [group|user] keyword");
135 aip->ai_idtype = ACL_IDTYPE_WORLD;
139 case ACL_MODECHG_ADD_CHR:
140 *modechg = ACL_MODECHG_ADD;
142 case ACL_MODECHG_DEL_CHR:
143 *modechg = ACL_MODECHG_DEL;
145 case ACL_MODECHG_EQL_CHR:
146 *modechg = ACL_MODECHG_EQL;
149 elog(ERROR, "aclparse: mode change flag must use \"%s\"",
153 aip->ai_mode = ACL_NO;
154 while (isalpha((unsigned char) *++s))
158 case ACL_MODE_AP_CHR:
159 aip->ai_mode |= ACL_AP;
161 case ACL_MODE_RD_CHR:
162 aip->ai_mode |= ACL_RD;
164 case ACL_MODE_WR_CHR:
165 aip->ai_mode |= ACL_WR;
167 case ACL_MODE_RU_CHR:
168 aip->ai_mode |= ACL_RU;
171 elog(ERROR, "aclparse: mode flags must use \"%s\"",
176 switch (aip->ai_idtype)
179 htup = SearchSysCache(SHADOWNAME,
180 PointerGetDatum(name),
182 if (!HeapTupleIsValid(htup))
183 elog(ERROR, "aclparse: non-existent user \"%s\"", name);
184 aip->ai_id = ((Form_pg_shadow) GETSTRUCT(htup))->usesysid;
185 ReleaseSysCache(htup);
188 aip->ai_id = get_grosysid(name);
190 case ACL_IDTYPE_WORLD:
191 aip->ai_id = ACL_ID_WORLD;
195 #ifdef ACLDEBUG_TRACE
196 elog(DEBUG, "aclparse: correctly read [%x %d %x], modechg=%x",
197 aip->ai_idtype, aip->ai_id, aip->ai_mode, *modechg);
204 * Allocates storage for a new Acl with 'n' entries.
216 elog(ERROR, "makeacl: invalid size: %d", n);
217 size = ACL_N_SIZE(n);
218 new_acl = (Acl *) palloc(size);
219 MemSet((char *) new_acl, 0, size);
220 new_acl->size = size;
223 ARR_LBOUND(new_acl)[0] = 0;
224 ARR_DIMS(new_acl)[0] = n;
230 * Allocates storage for, and fills in, a new AclItem given a string
231 * 's' that contains an ACL specification. See aclparse for details.
237 aclitemin(PG_FUNCTION_ARGS)
239 char *s = PG_GETARG_CSTRING(0);
243 aip = (AclItem *) palloc(sizeof(AclItem));
244 s = aclparse(s, aip, &modechg);
245 if (modechg != ACL_MODECHG_EQL)
246 elog(ERROR, "aclitemin: cannot accept anything but = ACLs");
247 while (isspace((unsigned char) *s))
250 elog(ERROR, "aclitemin: extra garbage at end of specification");
251 PG_RETURN_ACLITEM_P(aip);
256 * Allocates storage for, and fills in, a new null-delimited string
257 * containing a formatted ACL specification. See aclparse for details.
263 aclitemout(PG_FUNCTION_ARGS)
265 AclItem *aip = PG_GETARG_ACLITEM_P(0);
272 p = out = palloc(strlen("group =arwR ") + 1 + NAMEDATALEN);
275 switch (aip->ai_idtype)
278 htup = SearchSysCache(SHADOWSYSID,
279 ObjectIdGetDatum(aip->ai_id),
281 if (HeapTupleIsValid(htup))
284 NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename),
286 ReleaseSysCache(htup);
290 /* Generate numeric UID if we don't find an entry */
293 tmp = DatumGetCString(DirectFunctionCall1(int4out,
294 Int32GetDatum((int32) aip->ai_id)));
301 tmpname = get_groname(aip->ai_id);
303 strncat(p, tmpname, NAMEDATALEN);
306 /* Generate numeric GID if we don't find an entry */
309 tmp = DatumGetCString(DirectFunctionCall1(int4out,
310 Int32GetDatum((int32) aip->ai_id)));
315 case ACL_IDTYPE_WORLD:
318 elog(ERROR, "aclitemout: bad ai_idtype: %d", aip->ai_idtype);
324 for (i = 0; i < N_ACL_MODES; ++i)
325 if ((aip->ai_mode >> i) & 01)
326 *p++ = ACL_MODE_STR[i];
329 PG_RETURN_CSTRING(out);
335 * AclItem equality and greater-than comparison routines.
336 * Two AclItems are considered equal iff they have the
337 * same identifier (and identifier type); the mode is ignored.
338 * Note that these routines are really only useful for sorting
339 * AclItems into identifier order.
342 * a boolean value indicating = or >
345 aclitemeq(AclItem *a1, AclItem *a2)
347 return a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id;
351 aclitemgt(AclItem *a1, AclItem *a2)
353 return ((a1->ai_idtype > a2->ai_idtype) ||
354 (a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id));
359 * acldefault() --- create an ACL describing default access permissions
361 * Change this routine if you want to alter the default access policy for
362 * newly-created tables (or any table with a NULL acl entry in pg_class)
365 acldefault(char *relname, AclId ownerid)
370 #define ACL_WORLD_DEFAULT (ACL_NO)
371 /* #define ACL_WORLD_DEFAULT (ACL_RD|ACL_WR|ACL_AP|ACL_RU) */
372 #define ACL_OWNER_DEFAULT (ACL_RD|ACL_WR|ACL_AP|ACL_RU)
376 aip[0].ai_idtype = ACL_IDTYPE_WORLD;
377 aip[0].ai_id = ACL_ID_WORLD;
378 aip[0].ai_mode = IsSystemRelationName(relname) ? ACL_RD : ACL_WORLD_DEFAULT;
379 aip[1].ai_idtype = ACL_IDTYPE_UID;
380 aip[1].ai_id = ownerid;
381 aip[1].ai_mode = ACL_OWNER_DEFAULT;
387 * Add or replace an item in an ACL array.
389 * NB: caller is responsible for having detoasted the input ACL, if needed.
392 aclinsert3(Acl *old_acl, AclItem *mod_aip, unsigned modechg)
401 /* These checks for null input are probably dead code, but... */
402 if (!old_acl || ACL_NUM(old_acl) < 1)
403 old_acl = makeacl(1);
406 new_acl = makeacl(ACL_NUM(old_acl));
407 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
411 num = ACL_NUM(old_acl);
412 old_aip = ACL_DAT(old_acl);
415 * Search the ACL for an existing entry for 'id'. If one exists, just
416 * modify the entry in-place (well, in the same position, since we
417 * actually return a copy); otherwise, insert the new entry in
420 /* find the first element not less than the element to be inserted */
421 for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
424 if (dst < num && aclitemeq(mod_aip, old_aip + dst))
426 /* modify in-place */
427 new_acl = makeacl(num);
428 new_aip = ACL_DAT(new_acl);
429 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
434 new_acl = makeacl(num + 1);
435 new_aip = ACL_DAT(new_acl);
438 elog(ERROR, "aclinsert3: insertion before world ACL??");
442 memcpy((char *) new_aip,
444 num * sizeof(AclItem));
448 memcpy((char *) new_aip,
450 dst * sizeof(AclItem));
451 memcpy((char *) (new_aip + dst + 1),
452 (char *) (old_aip + dst),
453 (num - dst) * sizeof(AclItem));
455 new_aip[dst].ai_id = mod_aip->ai_id;
456 new_aip[dst].ai_idtype = mod_aip->ai_idtype;
457 num++; /* set num to the size of new_acl */
458 src = 0; /* if add or del, start from world entry */
461 /* apply the permissions mod */
464 case ACL_MODECHG_ADD:
465 new_aip[dst].ai_mode = old_aip[src].ai_mode | mod_aip->ai_mode;
467 case ACL_MODECHG_DEL:
468 new_aip[dst].ai_mode = old_aip[src].ai_mode & ~mod_aip->ai_mode;
470 case ACL_MODECHG_EQL:
471 new_aip[dst].ai_mode = mod_aip->ai_mode;
476 * if the adjusted entry has no permissions, delete it from the
477 * list. For example, this helps in removing entries for users who no
478 * longer exist. EXCEPTION: never remove the world entry.
480 if (new_aip[dst].ai_mode == 0 && dst > 0)
484 for (i = dst + 1; i < num; i++)
486 new_aip[i - 1].ai_id = new_aip[i].ai_id;
487 new_aip[i - 1].ai_idtype = new_aip[i].ai_idtype;
488 new_aip[i - 1].ai_mode = new_aip[i].ai_mode;
490 ARR_DIMS(new_acl)[0] = num - 1;
491 /* Adjust also the array size because it is used for memcpy */
492 ARR_SIZE(new_acl) -= sizeof(AclItem);
499 * aclinsert (exported function)
502 aclinsert(PG_FUNCTION_ARGS)
504 Acl *old_acl = PG_GETARG_ACL_P(0);
505 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
507 PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
511 aclremove(PG_FUNCTION_ARGS)
513 Acl *old_acl = PG_GETARG_ACL_P(0);
514 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
522 /* These checks for null input should be dead code, but... */
523 if (!old_acl || ACL_NUM(old_acl) < 1)
524 old_acl = makeacl(1);
527 new_acl = makeacl(ACL_NUM(old_acl));
528 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
529 PG_RETURN_ACL_P(new_acl);
532 old_num = ACL_NUM(old_acl);
533 old_aip = ACL_DAT(old_acl);
535 /* Search for the matching entry */
536 for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip + dst); ++dst)
541 /* Not found, so return copy of source ACL */
542 new_acl = makeacl(old_num);
543 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
547 new_num = old_num - 1;
548 new_acl = makeacl(new_num);
549 new_aip = ACL_DAT(new_acl);
552 elog(ERROR, "aclremove: removal of the world ACL??");
554 else if (dst == old_num - 1)
556 memcpy((char *) new_aip,
558 new_num * sizeof(AclItem));
562 memcpy((char *) new_aip,
564 dst * sizeof(AclItem));
565 memcpy((char *) (new_aip + dst),
566 (char *) (old_aip + dst + 1),
567 (new_num - dst) * sizeof(AclItem));
571 PG_RETURN_ACL_P(new_acl);
575 aclcontains(PG_FUNCTION_ARGS)
577 Acl *acl = PG_GETARG_ACL_P(0);
578 AclItem *aip = PG_GETARG_ACLITEM_P(1);
584 aidat = ACL_DAT(acl);
585 for (i = 0; i < num; ++i)
587 /* Note that aclitemeq only considers id, not mode */
588 if (aclitemeq(aip, aidat + i) &&
589 aip->ai_mode == aidat[i].ai_mode)
590 PG_RETURN_BOOL(true);
592 PG_RETURN_BOOL(false);
596 * ExecuteChangeACLStmt
597 * Called to execute the utility command type ChangeACLStmt
600 ExecuteChangeACLStmt(ChangeACLStmt *stmt)
606 /* see comment in pg_type.h */
607 Assert(ACLITEMSIZE == sizeof(AclItem));
609 /* Convert string ACL spec into internal form */
610 aclparse(stmt->aclString, &aclitem, &modechg);
612 foreach(i, stmt->relNames)
614 char *relname = strVal(lfirst(i));
617 rel = heap_openr(relname, AccessExclusiveLock);
618 if (rel && rel->rd_rel->relkind == RELKIND_INDEX)
619 elog(ERROR, "\"%s\" is an index relation",
621 if (!pg_ownercheck(GetUserId(), relname, RELNAME))
622 elog(ERROR, "you do not own class \"%s\"",
624 ChangeAcl(relname, &aclitem, modechg);
625 /* close rel, but keep lock until end of xact */
626 heap_close(rel, NoLock);
632 * Parser support routines for ACL-related statements.
634 * XXX CAUTION: these are called from gram.y, which is not allowed to
635 * do any table accesses. Therefore, it is not kosher to do things
636 * like trying to translate usernames to user IDs here. Keep it all
637 * in string form until statement execution time.
642 * make a acl privilege string out of an existing privilege string
643 * and a new privilege
645 * does not add duplicate privileges
648 aclmakepriv(char *old_privlist, char new_priv)
654 Assert(strlen(old_privlist) < 5);
655 priv = palloc(5); /* at most "rwaR" */ ;
657 if (old_privlist == NULL || old_privlist[0] == '\0')
664 strcpy(priv, old_privlist);
666 l = strlen(old_privlist);
669 { /* can't add any more privileges */
673 /* check to see if the new privilege is already in the old string */
674 for (i = 0; i < l; i++)
676 if (priv[i] == new_priv)
680 { /* we really have a new privilege */
690 * user_type must be "A" - all users
694 * Just concatenates the two strings together with a space in between.
695 * Per above comments, we can't try to resolve a user or group name here.
698 aclmakeuser(char *user_type, char *user)
702 user_list = palloc(strlen(user_type) + strlen(user) + 2);
703 sprintf(user_list, "%s %s", user_type, user);
709 * create a ChangeACLStmt at parse time.
710 * we take in the privileges, relation_name_list, and grantee
711 * as well as a single character '+' or '-' to indicate grant or revoke
713 * We convert the information to the same external form recognized by
714 * aclitemin (see aclparse), and save that string in the ChangeACLStmt.
715 * Conversion to internal form happens when the statement is executed.
718 makeAclStmt(char *privileges, List *rel_list, char *grantee,
719 char grant_or_revoke)
721 ChangeACLStmt *n = makeNode(ChangeACLStmt);
724 initStringInfo(&str);
726 /* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */
727 if (grantee[0] == 'G') /* group permissions */
729 appendStringInfo(&str, "%s \"%s\"%c%s",
730 ACL_IDTYPE_GID_KEYWORD,
731 grantee + 2, grant_or_revoke, privileges);
733 else if (grantee[0] == 'U') /* user permission */
735 appendStringInfo(&str, "%s \"%s\"%c%s",
736 ACL_IDTYPE_UID_KEYWORD,
737 grantee + 2, grant_or_revoke, privileges);
742 appendStringInfo(&str, "%c%s",
743 grant_or_revoke, privileges);
745 n->relNames = rel_list;
746 n->aclString = pstrdup(str.data);