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.67 2002/02/18 23:11:22 petere 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/lsyscache.h"
29 #include "utils/syscache.h"
32 #define ACL_IDTYPE_GID_KEYWORD "group"
33 #define ACL_IDTYPE_UID_KEYWORD "user"
35 static const char *getid(const char *s, char *n);
36 static bool aclitemeq(const AclItem *a1, const AclItem *a2);
37 static bool aclitemgt(const AclItem *a1, const AclItem *a2);
39 static AclMode convert_priv_string(text *priv_type_text);
40 static bool has_table_privilege_cname_cname(char *username, char *relname,
41 text *priv_type_text);
42 static bool has_table_privilege_cname_id(char *username, Oid reloid,
43 text *priv_type_text);
44 static bool has_table_privilege_id_cname(int32 usesysid, char *relname,
45 text *priv_type_text);
50 * Consumes the first alphanumeric string (identifier) found in string
51 * 's', ignoring any leading white space. If it finds a double quote
52 * it returns the word inside the quotes.
55 * the string position in 's' that points to the next non-space character
56 * in 's', after any quotes. Also:
57 * - loads the identifier into 'name'. (If no identifier is found, 'name'
58 * contains an empty string.) name must be NAMEDATALEN bytes.
61 getid(const char *s, char *n)
69 while (isspace((unsigned char) *s))
79 isalnum((unsigned char) *s) || *s == '_' || in_quotes;
82 if (in_quotes && *s == '"')
88 if (len >= NAMEDATALEN)
89 elog(ERROR, "getid: identifier must be <%d characters",
94 while (isspace((unsigned char) *s))
101 * Consumes and parses an ACL specification of the form:
102 * [group|user] [A-Za-z0-9]*[+-=][rwaR]*
103 * from string 's', ignoring any leading white space or white space
104 * between the optional id type keyword (group|user) and the actual
107 * This routine is called by the parser as well as aclitemin(), hence
108 * the added generality.
111 * the string position in 's' immediately following the ACL
112 * specification. Also:
113 * - loads the structure pointed to by 'aip' with the appropriate
114 * UID/GID, id type identifier and mode type values.
115 * - loads 'modechg' with the mode change flag.
118 aclparse(const char *s, AclItem *aip, unsigned *modechg)
120 char name[NAMEDATALEN];
122 Assert(s && aip && modechg);
125 elog(DEBUG, "aclparse: input = '%s'", s);
127 aip->ai_idtype = ACL_IDTYPE_UID;
129 if (*s != ACL_MODECHG_ADD_CHR &&
130 *s != ACL_MODECHG_DEL_CHR &&
131 *s != ACL_MODECHG_EQL_CHR)
133 /* we just read a keyword, not a name */
134 if (!strcmp(name, ACL_IDTYPE_GID_KEYWORD))
135 aip->ai_idtype = ACL_IDTYPE_GID;
136 else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD))
137 elog(ERROR, "aclparse: bad keyword, must be [group|user]");
138 s = getid(s, name); /* move s to the name beyond the keyword */
140 elog(ERROR, "aclparse: a name must follow the [group|user] keyword");
143 aip->ai_idtype = ACL_IDTYPE_WORLD;
147 case ACL_MODECHG_ADD_CHR:
148 *modechg = ACL_MODECHG_ADD;
150 case ACL_MODECHG_DEL_CHR:
151 *modechg = ACL_MODECHG_DEL;
153 case ACL_MODECHG_EQL_CHR:
154 *modechg = ACL_MODECHG_EQL;
157 elog(ERROR, "aclparse: mode change flag must use \"%s\"",
161 aip->ai_mode = ACL_NO;
162 while (isalpha((unsigned char) *++s))
166 case ACL_MODE_INSERT_CHR:
167 aip->ai_mode |= ACL_INSERT;
169 case ACL_MODE_SELECT_CHR:
170 aip->ai_mode |= ACL_SELECT;
172 case ACL_MODE_UPDATE_CHR:
173 aip->ai_mode |= ACL_UPDATE;
175 case ACL_MODE_DELETE_CHR:
176 aip->ai_mode |= ACL_DELETE;
178 case ACL_MODE_RULE_CHR:
179 aip->ai_mode |= ACL_RULE;
181 case ACL_MODE_REFERENCES_CHR:
182 aip->ai_mode |= ACL_REFERENCES;
184 case ACL_MODE_TRIGGER_CHR:
185 aip->ai_mode |= ACL_TRIGGER;
188 elog(ERROR, "aclparse: mode flags must use \"%s\"",
193 switch (aip->ai_idtype)
196 aip->ai_id = get_usesysid(name);
199 aip->ai_id = get_grosysid(name);
201 case ACL_IDTYPE_WORLD:
202 aip->ai_id = ACL_ID_WORLD;
207 elog(DEBUG, "aclparse: correctly read [%x %d %x], modechg=%x",
208 aip->ai_idtype, aip->ai_id, aip->ai_mode, *modechg);
215 * Allocates storage for a new Acl with 'n' entries.
227 elog(ERROR, "makeacl: invalid size: %d", n);
228 size = ACL_N_SIZE(n);
229 new_acl = (Acl *) palloc(size);
230 MemSet((char *) new_acl, 0, size);
231 new_acl->size = size;
234 ARR_LBOUND(new_acl)[0] = 0;
235 ARR_DIMS(new_acl)[0] = n;
241 * Allocates storage for, and fills in, a new AclItem given a string
242 * 's' that contains an ACL specification. See aclparse for details.
248 aclitemin(PG_FUNCTION_ARGS)
250 const char *s = PG_GETARG_CSTRING(0);
254 aip = (AclItem *) palloc(sizeof(AclItem));
255 s = aclparse(s, aip, &modechg);
256 if (modechg != ACL_MODECHG_EQL)
257 elog(ERROR, "aclitemin: cannot accept anything but = ACLs");
258 while (isspace((unsigned char) *s))
261 elog(ERROR, "aclitemin: extra garbage at end of specification");
262 PG_RETURN_ACLITEM_P(aip);
267 * Allocates storage for, and fills in, a new null-delimited string
268 * containing a formatted ACL specification. See aclparse for details.
274 aclitemout(PG_FUNCTION_ARGS)
276 AclItem *aip = PG_GETARG_ACLITEM_P(0);
283 p = out = palloc(strlen("group =" ACL_MODE_STR " ") + 1 + NAMEDATALEN);
286 switch (aip->ai_idtype)
289 htup = SearchSysCache(SHADOWSYSID,
290 ObjectIdGetDatum(aip->ai_id),
292 if (HeapTupleIsValid(htup))
295 NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename),
297 ReleaseSysCache(htup);
301 /* Generate numeric UID if we don't find an entry */
304 tmp = DatumGetCString(DirectFunctionCall1(int4out,
305 Int32GetDatum((int32) aip->ai_id)));
312 tmpname = get_groname(aip->ai_id);
314 strncat(p, tmpname, NAMEDATALEN);
317 /* Generate numeric GID if we don't find an entry */
320 tmp = DatumGetCString(DirectFunctionCall1(int4out,
321 Int32GetDatum((int32) aip->ai_id)));
326 case ACL_IDTYPE_WORLD:
329 elog(ERROR, "aclitemout: bad ai_idtype: %d", aip->ai_idtype);
335 for (i = 0; i < N_ACL_MODES; ++i)
336 if ((aip->ai_mode >> i) & 01)
337 *p++ = ACL_MODE_STR[i];
340 PG_RETURN_CSTRING(out);
346 * AclItem equality and greater-than comparison routines.
347 * Two AclItems are considered equal iff they have the
348 * same identifier (and identifier type); the mode is ignored.
349 * Note that these routines are really only useful for sorting
350 * AclItems into identifier order.
353 * a boolean value indicating = or >
356 aclitemeq(const AclItem *a1, const AclItem *a2)
358 return a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id;
362 aclitemgt(const AclItem *a1, const AclItem *a2)
364 return ((a1->ai_idtype > a2->ai_idtype) ||
365 (a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id));
370 * acldefault() --- create an ACL describing default access permissions
372 * Change this routine if you want to alter the default access policy for
373 * newly-created tables (or any table with a NULL acl entry in pg_class)
376 acldefault(AclId ownerid)
381 #define ACL_WORLD_DEFAULT (ACL_NO)
382 #define ACL_OWNER_DEFAULT (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_RULE|ACL_REFERENCES|ACL_TRIGGER)
384 acl = makeacl(ownerid ? 2 : 1);
386 aip[0].ai_idtype = ACL_IDTYPE_WORLD;
387 aip[0].ai_id = ACL_ID_WORLD;
388 aip[0].ai_mode = ACL_WORLD_DEFAULT;
389 /* FIXME: The owner's default should vary with the object type. */
392 aip[1].ai_idtype = ACL_IDTYPE_UID;
393 aip[1].ai_id = ownerid;
394 aip[1].ai_mode = ACL_OWNER_DEFAULT;
401 * Add or replace an item in an ACL array. The result is a modified copy;
402 * the input object is not changed.
404 * NB: caller is responsible for having detoasted the input ACL, if needed.
407 aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
415 /* These checks for null input are probably dead code, but... */
416 if (!old_acl || ACL_NUM(old_acl) < 1)
417 old_acl = makeacl(1);
420 new_acl = makeacl(ACL_NUM(old_acl));
421 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
425 num = ACL_NUM(old_acl);
426 old_aip = ACL_DAT(old_acl);
429 * Search the ACL for an existing entry for 'id'. If one exists, just
430 * modify the entry in-place (well, in the same position, since we
431 * actually return a copy); otherwise, insert the new entry in
434 /* find the first element not less than the element to be inserted */
435 for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
438 if (dst < num && aclitemeq(mod_aip, old_aip + dst))
440 /* found a match, so modify existing item */
441 new_acl = makeacl(num);
442 new_aip = ACL_DAT(new_acl);
443 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
447 /* need to insert a new item */
448 new_acl = makeacl(num + 1);
449 new_aip = ACL_DAT(new_acl);
452 elog(ERROR, "aclinsert3: insertion before world ACL??");
456 memcpy((char *) new_aip,
458 num * sizeof(AclItem));
462 memcpy((char *) new_aip,
464 dst * sizeof(AclItem));
465 memcpy((char *) (new_aip + dst + 1),
466 (char *) (old_aip + dst),
467 (num - dst) * sizeof(AclItem));
469 /* initialize the new entry with no permissions */
470 new_aip[dst].ai_id = mod_aip->ai_id;
471 new_aip[dst].ai_idtype = mod_aip->ai_idtype;
472 new_aip[dst].ai_mode = 0;
473 num++; /* set num to the size of new_acl */
476 /* apply the permissions mod */
479 case ACL_MODECHG_ADD:
480 new_aip[dst].ai_mode |= mod_aip->ai_mode;
482 case ACL_MODECHG_DEL:
483 new_aip[dst].ai_mode &= ~mod_aip->ai_mode;
485 case ACL_MODECHG_EQL:
486 new_aip[dst].ai_mode = mod_aip->ai_mode;
491 * if the adjusted entry has no permissions, delete it from the list.
492 * For example, this helps in removing entries for users who no longer
493 * exist. EXCEPTION: never remove the world entry.
495 if (new_aip[dst].ai_mode == 0 && dst > 0)
497 memmove((char *) (new_aip + dst),
498 (char *) (new_aip + dst + 1),
499 (num - dst - 1) * sizeof(AclItem));
500 ARR_DIMS(new_acl)[0] = num - 1;
501 ARR_SIZE(new_acl) -= sizeof(AclItem);
508 * aclinsert (exported function)
511 aclinsert(PG_FUNCTION_ARGS)
513 Acl *old_acl = PG_GETARG_ACL_P(0);
514 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
516 PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
520 aclremove(PG_FUNCTION_ARGS)
522 Acl *old_acl = PG_GETARG_ACL_P(0);
523 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
531 /* These checks for null input should be dead code, but... */
532 if (!old_acl || ACL_NUM(old_acl) < 1)
533 old_acl = makeacl(1);
536 new_acl = makeacl(ACL_NUM(old_acl));
537 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
538 PG_RETURN_ACL_P(new_acl);
541 old_num = ACL_NUM(old_acl);
542 old_aip = ACL_DAT(old_acl);
544 /* Search for the matching entry */
545 for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip + dst); ++dst)
550 /* Not found, so return copy of source ACL */
551 new_acl = makeacl(old_num);
552 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
556 new_num = old_num - 1;
557 new_acl = makeacl(new_num);
558 new_aip = ACL_DAT(new_acl);
561 elog(ERROR, "aclremove: removal of the world ACL??");
563 else if (dst == old_num - 1)
565 memcpy((char *) new_aip,
567 new_num * sizeof(AclItem));
571 memcpy((char *) new_aip,
573 dst * sizeof(AclItem));
574 memcpy((char *) (new_aip + dst),
575 (char *) (old_aip + dst + 1),
576 (new_num - dst) * sizeof(AclItem));
580 PG_RETURN_ACL_P(new_acl);
584 aclcontains(PG_FUNCTION_ARGS)
586 Acl *acl = PG_GETARG_ACL_P(0);
587 AclItem *aip = PG_GETARG_ACLITEM_P(1);
593 aidat = ACL_DAT(acl);
594 for (i = 0; i < num; ++i)
596 /* Note that aclitemeq only considers id, not mode */
597 if (aclitemeq(aip, aidat + i) &&
598 aip->ai_mode == aidat[i].ai_mode)
599 PG_RETURN_BOOL(true);
601 PG_RETURN_BOOL(false);
606 * Parser support routines for ACL-related statements.
608 * XXX CAUTION: these are called from gram.y, which is not allowed to
609 * do any table accesses. Therefore, it is not kosher to do things
610 * like trying to translate usernames to user IDs here. Keep it all
611 * in string form until statement execution time.
616 * make a acl privilege string out of an existing privilege string
617 * and a new privilege
619 * does not add duplicate privileges
622 aclmakepriv(const char *old_privlist, char new_priv)
628 Assert(strlen(old_privlist) <= strlen(ACL_MODE_STR));
629 priv = palloc(strlen(ACL_MODE_STR) + 1);
631 if (old_privlist == NULL || old_privlist[0] == '\0')
638 strcpy(priv, old_privlist);
640 l = strlen(old_privlist);
642 if (l == strlen(ACL_MODE_STR))
643 { /* can't add any more privileges */
647 /* check to see if the new privilege is already in the old string */
648 for (i = 0; i < l; i++)
650 if (priv[i] == new_priv)
654 { /* we really have a new privilege */
664 * user_type must be "A" - all users
668 * Just concatenates the two strings together with a space in between.
669 * Per above comments, we can't try to resolve a user or group name here.
672 aclmakeuser(const char *user_type, const char *user)
676 user_list = palloc(strlen(user_type) + strlen(user) + 2);
677 sprintf(user_list, "%s %s", user_type, user);
683 * makeAclString: We take in the privileges and grantee as well as a
684 * single character '+' or '-' to indicate grant or revoke.
686 * We convert the information to the same external form recognized by
687 * aclitemin (see aclparse) and return that string. Conversion to
688 * internal form happens when the statement is executed.
691 makeAclString(const char *privileges, const char *grantee, char grant_or_revoke)
696 initStringInfo(&str);
698 /* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */
699 if (grantee[0] == 'G') /* group permissions */
701 appendStringInfo(&str, "%s \"%s\"%c%s",
702 ACL_IDTYPE_GID_KEYWORD,
703 grantee + 2, grant_or_revoke, privileges);
705 else if (grantee[0] == 'U') /* user permission */
707 appendStringInfo(&str, "%s \"%s\"%c%s",
708 ACL_IDTYPE_UID_KEYWORD,
709 grantee + 2, grant_or_revoke, privileges);
714 appendStringInfo(&str, "%c%s",
715 grant_or_revoke, privileges);
717 ret = pstrdup(str.data);
724 * has_table_privilege_name_name
725 * Check user privileges on a relation given
726 * name usename, name relname, and text priv name.
730 * 't' indicating user has the privilege
731 * 'f' indicating user does not have the privilege
734 has_table_privilege_name_name(PG_FUNCTION_ARGS)
736 Name username = PG_GETARG_NAME(0);
737 Name relname = PG_GETARG_NAME(1);
738 text *priv_type_text = PG_GETARG_TEXT_P(2);
741 result = has_table_privilege_cname_cname(NameStr(*username),
745 PG_RETURN_BOOL(result);
750 * has_table_privilege_name
751 * Check user privileges on a relation given
752 * name relname and text priv name.
753 * current_user is assumed
757 * 't' indicating user has the privilege
758 * 'f' indicating user does not have the privilege
761 has_table_privilege_name(PG_FUNCTION_ARGS)
763 Name relname = PG_GETARG_NAME(0);
764 text *priv_type_text = PG_GETARG_TEXT_P(1);
768 usesysid = GetUserId();
770 result = has_table_privilege_id_cname(usesysid,
774 PG_RETURN_BOOL(result);
779 * has_table_privilege_name_id
780 * Check user privileges on a relation given
781 * name usename, rel oid, and text priv name.
785 * 't' indicating user has the privilege
786 * 'f' indicating user does not have the privilege
789 has_table_privilege_name_id(PG_FUNCTION_ARGS)
791 Name username = PG_GETARG_NAME(0);
792 Oid reloid = PG_GETARG_OID(1);
793 text *priv_type_text = PG_GETARG_TEXT_P(2);
796 result = has_table_privilege_cname_id(NameStr(*username),
800 PG_RETURN_BOOL(result);
805 * has_table_privilege_id
806 * Check user privileges on a relation given
807 * rel oid, and text priv name.
808 * current_user is assumed
812 * 't' indicating user has the privilege
813 * 'f' indicating user does not have the privilege
816 has_table_privilege_id(PG_FUNCTION_ARGS)
818 Oid reloid = PG_GETARG_OID(0);
819 text *priv_type_text = PG_GETARG_TEXT_P(1);
825 usesysid = GetUserId();
828 * Lookup relname based on rel oid
830 relname = get_rel_name(reloid);
832 elog(ERROR, "has_table_privilege: invalid relation oid %u",
836 * Convert priv_type_text to an AclMode
838 mode = convert_priv_string(priv_type_text);
841 * Finally, check for the privilege
843 result = pg_aclcheck(relname, usesysid, mode);
845 if (result == ACLCHECK_OK)
846 PG_RETURN_BOOL(true);
848 PG_RETURN_BOOL(false);
853 * has_table_privilege_id_name
854 * Check user privileges on a relation given
855 * usesysid, name relname, and priv name.
859 * 't' indicating user has the privilege
860 * 'f' indicating user does not have the privilege
863 has_table_privilege_id_name(PG_FUNCTION_ARGS)
865 int32 usesysid = PG_GETARG_INT32(0);
866 Name relname = PG_GETARG_NAME(1);
867 text *priv_type_text = PG_GETARG_TEXT_P(2);
870 result = has_table_privilege_id_cname(usesysid,
874 PG_RETURN_BOOL(result);
879 * has_table_privilege_id_id
880 * Check user privileges on a relation given
881 * usesysid, rel oid, and priv name.
885 * 't' indicating user has the privilege
886 * 'f' indicating user does not have the privilege
889 has_table_privilege_id_id(PG_FUNCTION_ARGS)
891 int32 usesysid = PG_GETARG_INT32(0);
892 Oid reloid = PG_GETARG_OID(1);
893 text *priv_type_text = PG_GETARG_TEXT_P(2);
899 * Lookup relname based on rel oid
901 relname = get_rel_name(reloid);
903 elog(ERROR, "has_table_privilege: invalid relation oid %u",
907 * Convert priv_type_text to an AclMode
909 mode = convert_priv_string(priv_type_text);
912 * Finally, check for the privilege
914 result = pg_aclcheck(relname, usesysid, mode);
916 if (result == ACLCHECK_OK)
917 PG_RETURN_BOOL(true);
919 PG_RETURN_BOOL(false);
923 * Internal functions.
927 * convert_priv_string
929 * Return mode from priv_type string
936 convert_priv_string(text *priv_type_text)
938 char *priv_type = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(priv_type_text)));
941 * Return mode from priv_type string
943 if (strcasecmp(priv_type, "SELECT") == 0)
946 if (strcasecmp(priv_type, "INSERT") == 0)
949 if (strcasecmp(priv_type, "UPDATE") == 0)
952 if (strcasecmp(priv_type, "DELETE") == 0)
955 if (strcasecmp(priv_type, "RULE") == 0)
958 if (strcasecmp(priv_type, "REFERENCES") == 0)
959 return ACL_REFERENCES;
961 if (strcasecmp(priv_type, "TRIGGER") == 0)
964 elog(ERROR, "has_table_privilege: invalid privilege type %s", priv_type);
967 * We should never get here, but stop the compiler from complaining
973 * has_table_privilege_cname_cname
974 * Check user privileges on a relation given
975 * char *usename, char *relname, and text priv name.
979 * 't' indicating user has the privilege
980 * 'f' indicating user does not have the privilege
983 has_table_privilege_cname_cname(char *username, char *relname,
984 text *priv_type_text)
989 * Lookup userid based on username
991 usesysid = get_usesysid(username);
994 * Make use of has_table_privilege_id_cname. It accepts the arguments
997 return has_table_privilege_id_cname(usesysid, relname, priv_type_text);
1002 * has_table_privilege_cname_id
1003 * Check user privileges on a relation given
1004 * char *usename, rel oid, and text priv name.
1008 * 't' indicating user has the privilege
1009 * 'f' indicating user does not have the privilege
1012 has_table_privilege_cname_id(char *username, Oid reloid,
1013 text *priv_type_text)
1019 * Lookup userid based on username
1021 usesysid = get_usesysid(username);
1024 * Lookup relname based on rel oid
1026 relname = get_rel_name(reloid);
1027 if (relname == NULL)
1028 elog(ERROR, "has_table_privilege: invalid relation oid %u",
1032 * Make use of has_table_privilege_id_cname. It accepts the arguments
1035 return has_table_privilege_id_cname(usesysid, relname, priv_type_text);
1040 * has_table_privilege_id_cname
1041 * Check user privileges on a relation given
1042 * usesysid, char *relname, and text priv name.
1046 * 't' indicating user has the privilege
1047 * 'f' indicating user does not have the privilege
1050 has_table_privilege_id_cname(int32 usesysid, char *relname,
1051 text *priv_type_text)
1058 * Check relname is valid. This is needed to deal with the case when
1059 * usename is a superuser in which case pg_aclcheck simply returns
1060 * ACLCHECK_OK without validating relname
1062 tuple = SearchSysCache(RELNAME,
1063 PointerGetDatum(relname),
1065 if (!HeapTupleIsValid(tuple))
1066 elog(ERROR, "has_table_privilege: relation \"%s\" does not exist",
1068 ReleaseSysCache(tuple);
1071 * Convert priv_type_text to an AclMode
1073 mode = convert_priv_string(priv_type_text);
1076 * Finally, check for the privilege
1078 result = pg_aclcheck(relname, usesysid, mode);
1080 if (result == ACLCHECK_OK)