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.63 2001/06/12 16:34:26 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 const char *getid(const char *s, char *n);
31 static bool aclitemeq(const AclItem *a1, const AclItem *a2);
32 static bool aclitemgt(const AclItem *a1, const AclItem *a2);
34 #define ACL_IDTYPE_GID_KEYWORD "group"
35 #define ACL_IDTYPE_UID_KEYWORD "user"
39 * Consumes the first alphanumeric string (identifier) found in string
40 * 's', ignoring any leading white space. If it finds a double quote
41 * it returns the word inside the quotes.
44 * the string position in 's' that points to the next non-space character
45 * in 's', after any quotes. Also:
46 * - loads the identifier into 'name'. (If no identifier is found, 'name'
47 * contains an empty string.) name must be NAMEDATALEN bytes.
50 getid(const char *s, char *n)
58 while (isspace((unsigned char) *s))
68 isalnum((unsigned char) *s) || *s == '_' || in_quotes;
71 if (in_quotes && *s == '"')
77 if (len >= NAMEDATALEN)
78 elog(ERROR, "getid: identifier must be <%d characters",
83 while (isspace((unsigned char) *s))
90 * Consumes and parses an ACL specification of the form:
91 * [group|user] [A-Za-z0-9]*[+-=][rwaR]*
92 * from string 's', ignoring any leading white space or white space
93 * between the optional id type keyword (group|user) and the actual
96 * This routine is called by the parser as well as aclitemin(), hence
97 * the added generality.
100 * the string position in 's' immediately following the ACL
101 * specification. Also:
102 * - loads the structure pointed to by 'aip' with the appropriate
103 * UID/GID, id type identifier and mode type values.
104 * - loads 'modechg' with the mode change flag.
107 aclparse(const char *s, AclItem *aip, unsigned *modechg)
110 char name[NAMEDATALEN];
112 Assert(s && aip && modechg);
115 elog(DEBUG, "aclparse: input = '%s'", s);
117 aip->ai_idtype = ACL_IDTYPE_UID;
119 if (*s != ACL_MODECHG_ADD_CHR &&
120 *s != ACL_MODECHG_DEL_CHR &&
121 *s != ACL_MODECHG_EQL_CHR)
123 /* we just read a keyword, not a name */
124 if (!strcmp(name, ACL_IDTYPE_GID_KEYWORD))
125 aip->ai_idtype = ACL_IDTYPE_GID;
126 else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD))
127 elog(ERROR, "aclparse: bad keyword, must be [group|user]");
128 s = getid(s, name); /* move s to the name beyond the keyword */
130 elog(ERROR, "aclparse: a name must follow the [group|user] keyword");
133 aip->ai_idtype = ACL_IDTYPE_WORLD;
137 case ACL_MODECHG_ADD_CHR:
138 *modechg = ACL_MODECHG_ADD;
140 case ACL_MODECHG_DEL_CHR:
141 *modechg = ACL_MODECHG_DEL;
143 case ACL_MODECHG_EQL_CHR:
144 *modechg = ACL_MODECHG_EQL;
147 elog(ERROR, "aclparse: mode change flag must use \"%s\"",
151 aip->ai_mode = ACL_NO;
152 while (isalpha((unsigned char) *++s))
156 case ACL_MODE_INSERT_CHR:
157 aip->ai_mode |= ACL_INSERT;
159 case ACL_MODE_SELECT_CHR:
160 aip->ai_mode |= ACL_SELECT;
162 case ACL_MODE_UPDATE_CHR:
163 aip->ai_mode |= ACL_UPDATE;
165 case ACL_MODE_DELETE_CHR:
166 aip->ai_mode |= ACL_DELETE;
168 case ACL_MODE_RULE_CHR:
169 aip->ai_mode |= ACL_RULE;
171 case ACL_MODE_REFERENCES_CHR:
172 aip->ai_mode |= ACL_REFERENCES;
174 case ACL_MODE_TRIGGER_CHR:
175 aip->ai_mode |= ACL_TRIGGER;
178 elog(ERROR, "aclparse: mode flags must use \"%s\"",
183 switch (aip->ai_idtype)
186 htup = SearchSysCache(SHADOWNAME,
187 PointerGetDatum(name),
189 if (!HeapTupleIsValid(htup))
190 elog(ERROR, "aclparse: non-existent user \"%s\"", name);
191 aip->ai_id = ((Form_pg_shadow) GETSTRUCT(htup))->usesysid;
192 ReleaseSysCache(htup);
195 aip->ai_id = get_grosysid(name);
197 case ACL_IDTYPE_WORLD:
198 aip->ai_id = ACL_ID_WORLD;
203 elog(DEBUG, "aclparse: correctly read [%x %d %x], modechg=%x",
204 aip->ai_idtype, aip->ai_id, aip->ai_mode, *modechg);
211 * Allocates storage for a new Acl with 'n' entries.
223 elog(ERROR, "makeacl: invalid size: %d", n);
224 size = ACL_N_SIZE(n);
225 new_acl = (Acl *) palloc(size);
226 MemSet((char *) new_acl, 0, size);
227 new_acl->size = size;
230 ARR_LBOUND(new_acl)[0] = 0;
231 ARR_DIMS(new_acl)[0] = n;
237 * Allocates storage for, and fills in, a new AclItem given a string
238 * 's' that contains an ACL specification. See aclparse for details.
244 aclitemin(PG_FUNCTION_ARGS)
246 const char *s = PG_GETARG_CSTRING(0);
250 aip = (AclItem *) palloc(sizeof(AclItem));
251 s = aclparse(s, aip, &modechg);
252 if (modechg != ACL_MODECHG_EQL)
253 elog(ERROR, "aclitemin: cannot accept anything but = ACLs");
254 while (isspace((unsigned char) *s))
257 elog(ERROR, "aclitemin: extra garbage at end of specification");
258 PG_RETURN_ACLITEM_P(aip);
263 * Allocates storage for, and fills in, a new null-delimited string
264 * containing a formatted ACL specification. See aclparse for details.
270 aclitemout(PG_FUNCTION_ARGS)
272 AclItem *aip = PG_GETARG_ACLITEM_P(0);
279 p = out = palloc(strlen("group =" ACL_MODE_STR " ") + 1 + NAMEDATALEN);
282 switch (aip->ai_idtype)
285 htup = SearchSysCache(SHADOWSYSID,
286 ObjectIdGetDatum(aip->ai_id),
288 if (HeapTupleIsValid(htup))
291 NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename),
293 ReleaseSysCache(htup);
297 /* Generate numeric UID if we don't find an entry */
300 tmp = DatumGetCString(DirectFunctionCall1(int4out,
301 Int32GetDatum((int32) aip->ai_id)));
308 tmpname = get_groname(aip->ai_id);
310 strncat(p, tmpname, NAMEDATALEN);
313 /* Generate numeric GID if we don't find an entry */
316 tmp = DatumGetCString(DirectFunctionCall1(int4out,
317 Int32GetDatum((int32) aip->ai_id)));
322 case ACL_IDTYPE_WORLD:
325 elog(ERROR, "aclitemout: bad ai_idtype: %d", aip->ai_idtype);
331 for (i = 0; i < N_ACL_MODES; ++i)
332 if ((aip->ai_mode >> i) & 01)
333 *p++ = ACL_MODE_STR[i];
336 PG_RETURN_CSTRING(out);
342 * AclItem equality and greater-than comparison routines.
343 * Two AclItems are considered equal iff they have the
344 * same identifier (and identifier type); the mode is ignored.
345 * Note that these routines are really only useful for sorting
346 * AclItems into identifier order.
349 * a boolean value indicating = or >
352 aclitemeq(const AclItem *a1, const AclItem *a2)
354 return a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id;
358 aclitemgt(const AclItem *a1, const AclItem *a2)
360 return ((a1->ai_idtype > a2->ai_idtype) ||
361 (a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id));
366 * acldefault() --- create an ACL describing default access permissions
368 * Change this routine if you want to alter the default access policy for
369 * newly-created tables (or any table with a NULL acl entry in pg_class)
372 acldefault(const char *relname, AclId ownerid)
377 #define ACL_WORLD_DEFAULT (ACL_NO)
378 #define ACL_OWNER_DEFAULT (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_RULE|ACL_REFERENCES|ACL_TRIGGER)
382 aip[0].ai_idtype = ACL_IDTYPE_WORLD;
383 aip[0].ai_id = ACL_ID_WORLD;
384 aip[0].ai_mode = IsSystemRelationName(relname) ? ACL_SELECT : ACL_WORLD_DEFAULT;
385 aip[1].ai_idtype = ACL_IDTYPE_UID;
386 aip[1].ai_id = ownerid;
387 aip[1].ai_mode = ACL_OWNER_DEFAULT;
393 * Add or replace an item in an ACL array. The result is a modified copy;
394 * the input object is not changed.
396 * NB: caller is responsible for having detoasted the input ACL, if needed.
399 aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
407 /* These checks for null input are probably dead code, but... */
408 if (!old_acl || ACL_NUM(old_acl) < 1)
409 old_acl = makeacl(1);
412 new_acl = makeacl(ACL_NUM(old_acl));
413 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
417 num = ACL_NUM(old_acl);
418 old_aip = ACL_DAT(old_acl);
421 * Search the ACL for an existing entry for 'id'. If one exists, just
422 * modify the entry in-place (well, in the same position, since we
423 * actually return a copy); otherwise, insert the new entry in
426 /* find the first element not less than the element to be inserted */
427 for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
430 if (dst < num && aclitemeq(mod_aip, old_aip + dst))
432 /* found a match, so modify existing item */
433 new_acl = makeacl(num);
434 new_aip = ACL_DAT(new_acl);
435 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
439 /* need to insert a new item */
440 new_acl = makeacl(num + 1);
441 new_aip = ACL_DAT(new_acl);
444 elog(ERROR, "aclinsert3: insertion before world ACL??");
448 memcpy((char *) new_aip,
450 num * sizeof(AclItem));
454 memcpy((char *) new_aip,
456 dst * sizeof(AclItem));
457 memcpy((char *) (new_aip + dst + 1),
458 (char *) (old_aip + dst),
459 (num - dst) * sizeof(AclItem));
461 /* initialize the new entry with no permissions */
462 new_aip[dst].ai_id = mod_aip->ai_id;
463 new_aip[dst].ai_idtype = mod_aip->ai_idtype;
464 new_aip[dst].ai_mode = 0;
465 num++; /* set num to the size of new_acl */
468 /* apply the permissions mod */
471 case ACL_MODECHG_ADD:
472 new_aip[dst].ai_mode |= mod_aip->ai_mode;
474 case ACL_MODECHG_DEL:
475 new_aip[dst].ai_mode &= ~mod_aip->ai_mode;
477 case ACL_MODECHG_EQL:
478 new_aip[dst].ai_mode = mod_aip->ai_mode;
483 * if the adjusted entry has no permissions, delete it from the list.
484 * For example, this helps in removing entries for users who no longer
485 * exist. EXCEPTION: never remove the world entry.
487 if (new_aip[dst].ai_mode == 0 && dst > 0)
489 memmove((char *) (new_aip + dst),
490 (char *) (new_aip + dst + 1),
491 (num - dst - 1) * sizeof(AclItem));
492 ARR_DIMS(new_acl)[0] = num - 1;
493 ARR_SIZE(new_acl) -= sizeof(AclItem);
500 * aclinsert (exported function)
503 aclinsert(PG_FUNCTION_ARGS)
505 Acl *old_acl = PG_GETARG_ACL_P(0);
506 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
508 PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
512 aclremove(PG_FUNCTION_ARGS)
514 Acl *old_acl = PG_GETARG_ACL_P(0);
515 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
523 /* These checks for null input should be dead code, but... */
524 if (!old_acl || ACL_NUM(old_acl) < 1)
525 old_acl = makeacl(1);
528 new_acl = makeacl(ACL_NUM(old_acl));
529 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
530 PG_RETURN_ACL_P(new_acl);
533 old_num = ACL_NUM(old_acl);
534 old_aip = ACL_DAT(old_acl);
536 /* Search for the matching entry */
537 for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip + dst); ++dst)
542 /* Not found, so return copy of source ACL */
543 new_acl = makeacl(old_num);
544 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
548 new_num = old_num - 1;
549 new_acl = makeacl(new_num);
550 new_aip = ACL_DAT(new_acl);
553 elog(ERROR, "aclremove: removal of the world ACL??");
555 else if (dst == old_num - 1)
557 memcpy((char *) new_aip,
559 new_num * sizeof(AclItem));
563 memcpy((char *) new_aip,
565 dst * sizeof(AclItem));
566 memcpy((char *) (new_aip + dst),
567 (char *) (old_aip + dst + 1),
568 (new_num - dst) * sizeof(AclItem));
572 PG_RETURN_ACL_P(new_acl);
576 aclcontains(PG_FUNCTION_ARGS)
578 Acl *acl = PG_GETARG_ACL_P(0);
579 AclItem *aip = PG_GETARG_ACLITEM_P(1);
585 aidat = ACL_DAT(acl);
586 for (i = 0; i < num; ++i)
588 /* Note that aclitemeq only considers id, not mode */
589 if (aclitemeq(aip, aidat + i) &&
590 aip->ai_mode == aidat[i].ai_mode)
591 PG_RETURN_BOOL(true);
593 PG_RETURN_BOOL(false);
598 * Parser support routines for ACL-related statements.
600 * XXX CAUTION: these are called from gram.y, which is not allowed to
601 * do any table accesses. Therefore, it is not kosher to do things
602 * like trying to translate usernames to user IDs here. Keep it all
603 * in string form until statement execution time.
608 * make a acl privilege string out of an existing privilege string
609 * and a new privilege
611 * does not add duplicate privileges
614 aclmakepriv(const char *old_privlist, char new_priv)
620 Assert(strlen(old_privlist) <= strlen(ACL_MODE_STR));
621 priv = palloc(strlen(ACL_MODE_STR)+1);
623 if (old_privlist == NULL || old_privlist[0] == '\0')
630 strcpy(priv, old_privlist);
632 l = strlen(old_privlist);
634 if (l == strlen(ACL_MODE_STR))
635 { /* can't add any more privileges */
639 /* check to see if the new privilege is already in the old string */
640 for (i = 0; i < l; i++)
642 if (priv[i] == new_priv)
646 { /* we really have a new privilege */
656 * user_type must be "A" - all users
660 * Just concatenates the two strings together with a space in between.
661 * Per above comments, we can't try to resolve a user or group name here.
664 aclmakeuser(const char *user_type, const char *user)
668 user_list = palloc(strlen(user_type) + strlen(user) + 2);
669 sprintf(user_list, "%s %s", user_type, user);
675 * makeAclString: We take in the privileges and grantee as well as a
676 * single character '+' or '-' to indicate grant or revoke.
678 * We convert the information to the same external form recognized by
679 * aclitemin (see aclparse) and return that string. Conversion to
680 * internal form happens when the statement is executed.
683 makeAclString(const char *privileges, const char *grantee, char grant_or_revoke)
688 initStringInfo(&str);
690 /* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */
691 if (grantee[0] == 'G') /* group permissions */
693 appendStringInfo(&str, "%s \"%s\"%c%s",
694 ACL_IDTYPE_GID_KEYWORD,
695 grantee + 2, grant_or_revoke, privileges);
697 else if (grantee[0] == 'U') /* user permission */
699 appendStringInfo(&str, "%s \"%s\"%c%s",
700 ACL_IDTYPE_UID_KEYWORD,
701 grantee + 2, grant_or_revoke, privileges);
706 appendStringInfo(&str, "%c%s",
707 grant_or_revoke, privileges);
709 ret = pstrdup(str.data);