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.70 2002/03/26 19:16:05 tgl Exp $
13 *-------------------------------------------------------------------------
19 #include "access/heapam.h"
20 #include "catalog/catalog.h"
21 #include "catalog/namespace.h"
22 #include "catalog/pg_shadow.h"
23 #include "catalog/pg_type.h"
24 #include "lib/stringinfo.h"
25 #include "miscadmin.h"
26 #include "utils/acl.h"
27 #include "utils/builtins.h"
28 #include "utils/memutils.h"
29 #include "utils/lsyscache.h"
30 #include "utils/syscache.h"
33 #define ACL_IDTYPE_GID_KEYWORD "group"
34 #define ACL_IDTYPE_UID_KEYWORD "user"
36 static const char *getid(const char *s, char *n);
37 static bool aclitemeq(const AclItem *a1, const AclItem *a2);
38 static bool aclitemgt(const AclItem *a1, const AclItem *a2);
40 static AclMode convert_priv_string(text *priv_type_text);
41 static bool has_table_privilege_cname_cname(char *username, char *relname,
42 text *priv_type_text);
43 static bool has_table_privilege_cname_id(char *username, Oid reloid,
44 text *priv_type_text);
45 static bool has_table_privilege_id_cname(int32 usesysid, char *relname,
46 text *priv_type_text);
51 * Consumes the first alphanumeric string (identifier) found in string
52 * 's', ignoring any leading white space. If it finds a double quote
53 * it returns the word inside the quotes.
56 * the string position in 's' that points to the next non-space character
57 * in 's', after any quotes. Also:
58 * - loads the identifier into 'name'. (If no identifier is found, 'name'
59 * contains an empty string.) name must be NAMEDATALEN bytes.
62 getid(const char *s, char *n)
70 while (isspace((unsigned char) *s))
80 isalnum((unsigned char) *s) || *s == '_' || in_quotes;
83 if (in_quotes && *s == '"')
89 if (len >= NAMEDATALEN)
90 elog(ERROR, "getid: identifier must be <%d characters",
95 while (isspace((unsigned char) *s))
102 * Consumes and parses an ACL specification of the form:
103 * [group|user] [A-Za-z0-9]*[+-=][rwaR]*
104 * from string 's', ignoring any leading white space or white space
105 * between the optional id type keyword (group|user) and the actual
108 * This routine is called by the parser as well as aclitemin(), hence
109 * the added generality.
112 * the string position in 's' immediately following the ACL
113 * specification. Also:
114 * - loads the structure pointed to by 'aip' with the appropriate
115 * UID/GID, id type identifier and mode type values.
116 * - loads 'modechg' with the mode change flag.
119 aclparse(const char *s, AclItem *aip, unsigned *modechg)
121 char name[NAMEDATALEN];
123 Assert(s && aip && modechg);
126 elog(LOG, "aclparse: input = '%s'", s);
128 aip->ai_idtype = ACL_IDTYPE_UID;
130 if (*s != ACL_MODECHG_ADD_CHR &&
131 *s != ACL_MODECHG_DEL_CHR &&
132 *s != ACL_MODECHG_EQL_CHR)
134 /* we just read a keyword, not a name */
135 if (!strcmp(name, ACL_IDTYPE_GID_KEYWORD))
136 aip->ai_idtype = ACL_IDTYPE_GID;
137 else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD))
138 elog(ERROR, "aclparse: bad keyword, must be [group|user]");
139 s = getid(s, name); /* move s to the name beyond the keyword */
141 elog(ERROR, "aclparse: a name must follow the [group|user] keyword");
144 aip->ai_idtype = ACL_IDTYPE_WORLD;
148 case ACL_MODECHG_ADD_CHR:
149 *modechg = ACL_MODECHG_ADD;
151 case ACL_MODECHG_DEL_CHR:
152 *modechg = ACL_MODECHG_DEL;
154 case ACL_MODECHG_EQL_CHR:
155 *modechg = ACL_MODECHG_EQL;
158 elog(ERROR, "aclparse: mode change flag must use \"%s\"",
162 aip->ai_mode = ACL_NO;
163 while (isalpha((unsigned char) *++s))
167 case ACL_MODE_INSERT_CHR:
168 aip->ai_mode |= ACL_INSERT;
170 case ACL_MODE_SELECT_CHR:
171 aip->ai_mode |= ACL_SELECT;
173 case ACL_MODE_UPDATE_CHR:
174 aip->ai_mode |= ACL_UPDATE;
176 case ACL_MODE_DELETE_CHR:
177 aip->ai_mode |= ACL_DELETE;
179 case ACL_MODE_RULE_CHR:
180 aip->ai_mode |= ACL_RULE;
182 case ACL_MODE_REFERENCES_CHR:
183 aip->ai_mode |= ACL_REFERENCES;
185 case ACL_MODE_TRIGGER_CHR:
186 aip->ai_mode |= ACL_TRIGGER;
189 elog(ERROR, "aclparse: mode flags must use \"%s\"",
194 switch (aip->ai_idtype)
197 aip->ai_id = get_usesysid(name);
200 aip->ai_id = get_grosysid(name);
202 case ACL_IDTYPE_WORLD:
203 aip->ai_id = ACL_ID_WORLD;
208 elog(LOG, "aclparse: correctly read [%x %d %x], modechg=%x",
209 aip->ai_idtype, aip->ai_id, aip->ai_mode, *modechg);
216 * Allocates storage for a new Acl with 'n' entries.
228 elog(ERROR, "makeacl: invalid size: %d", n);
229 size = ACL_N_SIZE(n);
230 new_acl = (Acl *) palloc(size);
231 MemSet((char *) new_acl, 0, size);
232 new_acl->size = size;
235 ARR_LBOUND(new_acl)[0] = 0;
236 ARR_DIMS(new_acl)[0] = n;
242 * Allocates storage for, and fills in, a new AclItem given a string
243 * 's' that contains an ACL specification. See aclparse for details.
249 aclitemin(PG_FUNCTION_ARGS)
251 const char *s = PG_GETARG_CSTRING(0);
255 aip = (AclItem *) palloc(sizeof(AclItem));
256 s = aclparse(s, aip, &modechg);
257 if (modechg != ACL_MODECHG_EQL)
258 elog(ERROR, "aclitemin: cannot accept anything but = ACLs");
259 while (isspace((unsigned char) *s))
262 elog(ERROR, "aclitemin: extra garbage at end of specification");
263 PG_RETURN_ACLITEM_P(aip);
268 * Allocates storage for, and fills in, a new null-delimited string
269 * containing a formatted ACL specification. See aclparse for details.
275 aclitemout(PG_FUNCTION_ARGS)
277 AclItem *aip = PG_GETARG_ACLITEM_P(0);
284 p = out = palloc(strlen("group =" ACL_MODE_STR " ") + 1 + NAMEDATALEN);
287 switch (aip->ai_idtype)
290 htup = SearchSysCache(SHADOWSYSID,
291 ObjectIdGetDatum(aip->ai_id),
293 if (HeapTupleIsValid(htup))
296 NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename),
298 ReleaseSysCache(htup);
302 /* Generate numeric UID if we don't find an entry */
305 tmp = DatumGetCString(DirectFunctionCall1(int4out,
306 Int32GetDatum((int32) aip->ai_id)));
313 tmpname = get_groname(aip->ai_id);
315 strncat(p, tmpname, NAMEDATALEN);
318 /* Generate numeric GID if we don't find an entry */
321 tmp = DatumGetCString(DirectFunctionCall1(int4out,
322 Int32GetDatum((int32) aip->ai_id)));
327 case ACL_IDTYPE_WORLD:
330 elog(ERROR, "aclitemout: bad ai_idtype: %d", aip->ai_idtype);
336 for (i = 0; i < N_ACL_MODES; ++i)
337 if ((aip->ai_mode >> i) & 01)
338 *p++ = ACL_MODE_STR[i];
341 PG_RETURN_CSTRING(out);
347 * AclItem equality and greater-than comparison routines.
348 * Two AclItems are considered equal iff they have the
349 * same identifier (and identifier type); the mode is ignored.
350 * Note that these routines are really only useful for sorting
351 * AclItems into identifier order.
354 * a boolean value indicating = or >
357 aclitemeq(const AclItem *a1, const AclItem *a2)
359 return a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id;
363 aclitemgt(const AclItem *a1, const AclItem *a2)
365 return ((a1->ai_idtype > a2->ai_idtype) ||
366 (a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id));
371 * acldefault() --- create an ACL describing default access permissions
373 * Change this routine if you want to alter the default access policy for
374 * newly-created tables (or any table with a NULL acl entry in pg_class)
377 acldefault(AclId ownerid)
382 #define ACL_WORLD_DEFAULT (ACL_NO)
383 #define ACL_OWNER_DEFAULT (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_RULE|ACL_REFERENCES|ACL_TRIGGER)
385 acl = makeacl(ownerid ? 2 : 1);
387 aip[0].ai_idtype = ACL_IDTYPE_WORLD;
388 aip[0].ai_id = ACL_ID_WORLD;
389 aip[0].ai_mode = ACL_WORLD_DEFAULT;
390 /* FIXME: The owner's default should vary with the object type. */
393 aip[1].ai_idtype = ACL_IDTYPE_UID;
394 aip[1].ai_id = ownerid;
395 aip[1].ai_mode = ACL_OWNER_DEFAULT;
402 * Add or replace an item in an ACL array. The result is a modified copy;
403 * the input object is not changed.
405 * NB: caller is responsible for having detoasted the input ACL, if needed.
408 aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
416 /* These checks for null input are probably dead code, but... */
417 if (!old_acl || ACL_NUM(old_acl) < 1)
418 old_acl = makeacl(1);
421 new_acl = makeacl(ACL_NUM(old_acl));
422 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
426 num = ACL_NUM(old_acl);
427 old_aip = ACL_DAT(old_acl);
430 * Search the ACL for an existing entry for 'id'. If one exists, just
431 * modify the entry in-place (well, in the same position, since we
432 * actually return a copy); otherwise, insert the new entry in
435 /* find the first element not less than the element to be inserted */
436 for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
439 if (dst < num && aclitemeq(mod_aip, old_aip + dst))
441 /* found a match, so modify existing item */
442 new_acl = makeacl(num);
443 new_aip = ACL_DAT(new_acl);
444 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
448 /* need to insert a new item */
449 new_acl = makeacl(num + 1);
450 new_aip = ACL_DAT(new_acl);
453 elog(ERROR, "aclinsert3: insertion before world ACL??");
457 memcpy((char *) new_aip,
459 num * sizeof(AclItem));
463 memcpy((char *) new_aip,
465 dst * sizeof(AclItem));
466 memcpy((char *) (new_aip + dst + 1),
467 (char *) (old_aip + dst),
468 (num - dst) * sizeof(AclItem));
470 /* initialize the new entry with no permissions */
471 new_aip[dst].ai_id = mod_aip->ai_id;
472 new_aip[dst].ai_idtype = mod_aip->ai_idtype;
473 new_aip[dst].ai_mode = 0;
474 num++; /* set num to the size of new_acl */
477 /* apply the permissions mod */
480 case ACL_MODECHG_ADD:
481 new_aip[dst].ai_mode |= mod_aip->ai_mode;
483 case ACL_MODECHG_DEL:
484 new_aip[dst].ai_mode &= ~mod_aip->ai_mode;
486 case ACL_MODECHG_EQL:
487 new_aip[dst].ai_mode = mod_aip->ai_mode;
492 * if the adjusted entry has no permissions, delete it from the list.
493 * For example, this helps in removing entries for users who no longer
494 * exist. EXCEPTION: never remove the world entry.
496 if (new_aip[dst].ai_mode == 0 && dst > 0)
498 memmove((char *) (new_aip + dst),
499 (char *) (new_aip + dst + 1),
500 (num - dst - 1) * sizeof(AclItem));
501 ARR_DIMS(new_acl)[0] = num - 1;
502 ARR_SIZE(new_acl) -= sizeof(AclItem);
509 * aclinsert (exported function)
512 aclinsert(PG_FUNCTION_ARGS)
514 Acl *old_acl = PG_GETARG_ACL_P(0);
515 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
517 PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
521 aclremove(PG_FUNCTION_ARGS)
523 Acl *old_acl = PG_GETARG_ACL_P(0);
524 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
532 /* These checks for null input should be dead code, but... */
533 if (!old_acl || ACL_NUM(old_acl) < 1)
534 old_acl = makeacl(1);
537 new_acl = makeacl(ACL_NUM(old_acl));
538 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
539 PG_RETURN_ACL_P(new_acl);
542 old_num = ACL_NUM(old_acl);
543 old_aip = ACL_DAT(old_acl);
545 /* Search for the matching entry */
546 for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip + dst); ++dst)
551 /* Not found, so return copy of source ACL */
552 new_acl = makeacl(old_num);
553 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
557 new_num = old_num - 1;
558 new_acl = makeacl(new_num);
559 new_aip = ACL_DAT(new_acl);
562 elog(ERROR, "aclremove: removal of the world ACL??");
564 else if (dst == old_num - 1)
566 memcpy((char *) new_aip,
568 new_num * sizeof(AclItem));
572 memcpy((char *) new_aip,
574 dst * sizeof(AclItem));
575 memcpy((char *) (new_aip + dst),
576 (char *) (old_aip + dst + 1),
577 (new_num - dst) * sizeof(AclItem));
581 PG_RETURN_ACL_P(new_acl);
585 aclcontains(PG_FUNCTION_ARGS)
587 Acl *acl = PG_GETARG_ACL_P(0);
588 AclItem *aip = PG_GETARG_ACLITEM_P(1);
594 aidat = ACL_DAT(acl);
595 for (i = 0; i < num; ++i)
597 /* Note that aclitemeq only considers id, not mode */
598 if (aclitemeq(aip, aidat + i) &&
599 aip->ai_mode == aidat[i].ai_mode)
600 PG_RETURN_BOOL(true);
602 PG_RETURN_BOOL(false);
607 * Parser support routines for ACL-related statements.
609 * XXX CAUTION: these are called from gram.y, which is not allowed to
610 * do any table accesses. Therefore, it is not kosher to do things
611 * like trying to translate usernames to user IDs here. Keep it all
612 * in string form until statement execution time.
617 * make a acl privilege string out of an existing privilege string
618 * and a new privilege
620 * does not add duplicate privileges
623 aclmakepriv(const char *old_privlist, char new_priv)
629 Assert(strlen(old_privlist) <= strlen(ACL_MODE_STR));
630 priv = palloc(strlen(ACL_MODE_STR) + 1);
632 if (old_privlist == NULL || old_privlist[0] == '\0')
639 strcpy(priv, old_privlist);
641 l = strlen(old_privlist);
643 if (l == strlen(ACL_MODE_STR))
644 { /* can't add any more privileges */
648 /* check to see if the new privilege is already in the old string */
649 for (i = 0; i < l; i++)
651 if (priv[i] == new_priv)
655 { /* we really have a new privilege */
665 * user_type must be "A" - all users
669 * Just concatenates the two strings together with a space in between.
670 * Per above comments, we can't try to resolve a user or group name here.
673 aclmakeuser(const char *user_type, const char *user)
677 user_list = palloc(strlen(user_type) + strlen(user) + 2);
678 sprintf(user_list, "%s %s", user_type, user);
684 * makeAclString: We take in the privileges and grantee as well as a
685 * single character '+' or '-' to indicate grant or revoke.
687 * We convert the information to the same external form recognized by
688 * aclitemin (see aclparse) and return that string. Conversion to
689 * internal form happens when the statement is executed.
692 makeAclString(const char *privileges, const char *grantee, char grant_or_revoke)
697 initStringInfo(&str);
699 /* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */
700 if (grantee[0] == 'G') /* group permissions */
702 appendStringInfo(&str, "%s \"%s\"%c%s",
703 ACL_IDTYPE_GID_KEYWORD,
704 grantee + 2, grant_or_revoke, privileges);
706 else if (grantee[0] == 'U') /* user permission */
708 appendStringInfo(&str, "%s \"%s\"%c%s",
709 ACL_IDTYPE_UID_KEYWORD,
710 grantee + 2, grant_or_revoke, privileges);
715 appendStringInfo(&str, "%c%s",
716 grant_or_revoke, privileges);
718 ret = pstrdup(str.data);
725 * has_table_privilege_name_name
726 * Check user privileges on a relation given
727 * name username, name relname, and text priv name.
731 * 't' indicating user has the privilege
732 * 'f' indicating user does not have the privilege
735 has_table_privilege_name_name(PG_FUNCTION_ARGS)
737 Name username = PG_GETARG_NAME(0);
738 Name relname = PG_GETARG_NAME(1);
739 text *priv_type_text = PG_GETARG_TEXT_P(2);
742 result = has_table_privilege_cname_cname(NameStr(*username),
746 PG_RETURN_BOOL(result);
751 * has_table_privilege_name
752 * Check user privileges on a relation given
753 * name relname and text priv name.
754 * current_user is assumed
758 * 't' indicating user has the privilege
759 * 'f' indicating user does not have the privilege
762 has_table_privilege_name(PG_FUNCTION_ARGS)
764 Name relname = PG_GETARG_NAME(0);
765 text *priv_type_text = PG_GETARG_TEXT_P(1);
769 usesysid = GetUserId();
771 result = has_table_privilege_id_cname(usesysid,
775 PG_RETURN_BOOL(result);
780 * has_table_privilege_name_id
781 * Check user privileges on a relation given
782 * name usename, rel oid, and text priv name.
786 * 't' indicating user has the privilege
787 * 'f' indicating user does not have the privilege
790 has_table_privilege_name_id(PG_FUNCTION_ARGS)
792 Name username = PG_GETARG_NAME(0);
793 Oid reloid = PG_GETARG_OID(1);
794 text *priv_type_text = PG_GETARG_TEXT_P(2);
797 result = has_table_privilege_cname_id(NameStr(*username),
801 PG_RETURN_BOOL(result);
806 * has_table_privilege_id
807 * Check user privileges on a relation given
808 * rel oid, and text priv name.
809 * current_user is assumed
813 * 't' indicating user has the privilege
814 * 'f' indicating user does not have the privilege
817 has_table_privilege_id(PG_FUNCTION_ARGS)
819 Oid reloid = PG_GETARG_OID(0);
820 text *priv_type_text = PG_GETARG_TEXT_P(1);
825 usesysid = GetUserId();
828 * Convert priv_type_text to an AclMode
830 mode = convert_priv_string(priv_type_text);
833 * Check for the privilege
835 result = pg_class_aclcheck(reloid, usesysid, mode);
837 if (result == ACLCHECK_OK)
838 PG_RETURN_BOOL(true);
840 PG_RETURN_BOOL(false);
845 * has_table_privilege_id_name
846 * Check user privileges on a relation given
847 * usesysid, name relname, and priv name.
851 * 't' indicating user has the privilege
852 * 'f' indicating user does not have the privilege
855 has_table_privilege_id_name(PG_FUNCTION_ARGS)
857 int32 usesysid = PG_GETARG_INT32(0);
858 Name relname = PG_GETARG_NAME(1);
859 text *priv_type_text = PG_GETARG_TEXT_P(2);
862 result = has_table_privilege_id_cname(usesysid,
866 PG_RETURN_BOOL(result);
871 * has_table_privilege_id_id
872 * Check user privileges on a relation given
873 * usesysid, rel oid, and priv name.
877 * 't' indicating user has the privilege
878 * 'f' indicating user does not have the privilege
881 has_table_privilege_id_id(PG_FUNCTION_ARGS)
883 int32 usesysid = PG_GETARG_INT32(0);
884 Oid reloid = PG_GETARG_OID(1);
885 text *priv_type_text = PG_GETARG_TEXT_P(2);
890 * Convert priv_type_text to an AclMode
892 mode = convert_priv_string(priv_type_text);
895 * Check for the privilege
897 result = pg_class_aclcheck(reloid, usesysid, mode);
899 if (result == ACLCHECK_OK)
900 PG_RETURN_BOOL(true);
902 PG_RETURN_BOOL(false);
906 * Internal functions.
910 * convert_priv_string
912 * Return mode from priv_type string
919 convert_priv_string(text *priv_type_text)
921 char *priv_type = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(priv_type_text)));
924 * Return mode from priv_type string
926 if (strcasecmp(priv_type, "SELECT") == 0)
929 if (strcasecmp(priv_type, "INSERT") == 0)
932 if (strcasecmp(priv_type, "UPDATE") == 0)
935 if (strcasecmp(priv_type, "DELETE") == 0)
938 if (strcasecmp(priv_type, "RULE") == 0)
941 if (strcasecmp(priv_type, "REFERENCES") == 0)
942 return ACL_REFERENCES;
944 if (strcasecmp(priv_type, "TRIGGER") == 0)
947 elog(ERROR, "has_table_privilege: invalid privilege type %s", priv_type);
950 * We should never get here, but stop the compiler from complaining
956 * has_table_privilege_cname_cname
957 * Check user privileges on a relation given
958 * char *usename, char *relname, and text priv name.
962 * 't' indicating user has the privilege
963 * 'f' indicating user does not have the privilege
966 has_table_privilege_cname_cname(char *username, char *relname,
967 text *priv_type_text)
972 * Lookup userid based on username
974 usesysid = get_usesysid(username);
977 * Make use of has_table_privilege_id_cname. It accepts the arguments
980 return has_table_privilege_id_cname(usesysid, relname, priv_type_text);
985 * has_table_privilege_cname_id
986 * Check user privileges on a relation given
987 * char *usename, rel oid, and text priv name.
991 * 't' indicating user has the privilege
992 * 'f' indicating user does not have the privilege
995 has_table_privilege_cname_id(char *username, Oid reloid,
996 text *priv_type_text)
1003 * Lookup userid based on username
1005 usesysid = get_usesysid(username);
1008 * Convert priv_type_text to an AclMode
1010 mode = convert_priv_string(priv_type_text);
1013 * Finally, check for the privilege
1015 result = pg_class_aclcheck(reloid, usesysid, mode);
1017 if (result == ACLCHECK_OK)
1025 * has_table_privilege_id_cname
1026 * Check user privileges on a relation given
1027 * usesysid, char *relname, and text priv name.
1031 * 't' indicating user has the privilege
1032 * 'f' indicating user does not have the privilege
1035 has_table_privilege_id_cname(int32 usesysid, char *relname,
1036 text *priv_type_text)
1043 * Convert relname to rel OID.
1045 reloid = RelnameGetRelid(relname);
1046 if (!OidIsValid(reloid))
1047 elog(ERROR, "has_table_privilege: relation \"%s\" does not exist",
1051 * Convert priv_type_text to an AclMode
1053 mode = convert_priv_string(priv_type_text);
1056 * Finally, check for the privilege
1058 result = pg_class_aclcheck(reloid, usesysid, mode);
1060 if (result == ACLCHECK_OK)