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.64 2001/06/14 01:09:22 tgl 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(const char *relname, 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)
386 aip[0].ai_idtype = ACL_IDTYPE_WORLD;
387 aip[0].ai_id = ACL_ID_WORLD;
388 aip[0].ai_mode = IsSystemRelationName(relname) ? ACL_SELECT : ACL_WORLD_DEFAULT;
389 aip[1].ai_idtype = ACL_IDTYPE_UID;
390 aip[1].ai_id = ownerid;
391 aip[1].ai_mode = ACL_OWNER_DEFAULT;
397 * Add or replace an item in an ACL array. The result is a modified copy;
398 * the input object is not changed.
400 * NB: caller is responsible for having detoasted the input ACL, if needed.
403 aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
411 /* These checks for null input are probably dead code, but... */
412 if (!old_acl || ACL_NUM(old_acl) < 1)
413 old_acl = makeacl(1);
416 new_acl = makeacl(ACL_NUM(old_acl));
417 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
421 num = ACL_NUM(old_acl);
422 old_aip = ACL_DAT(old_acl);
425 * Search the ACL for an existing entry for 'id'. If one exists, just
426 * modify the entry in-place (well, in the same position, since we
427 * actually return a copy); otherwise, insert the new entry in
430 /* find the first element not less than the element to be inserted */
431 for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
434 if (dst < num && aclitemeq(mod_aip, old_aip + dst))
436 /* found a match, so modify existing item */
437 new_acl = makeacl(num);
438 new_aip = ACL_DAT(new_acl);
439 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
443 /* need to insert a new item */
444 new_acl = makeacl(num + 1);
445 new_aip = ACL_DAT(new_acl);
448 elog(ERROR, "aclinsert3: insertion before world ACL??");
452 memcpy((char *) new_aip,
454 num * sizeof(AclItem));
458 memcpy((char *) new_aip,
460 dst * sizeof(AclItem));
461 memcpy((char *) (new_aip + dst + 1),
462 (char *) (old_aip + dst),
463 (num - dst) * sizeof(AclItem));
465 /* initialize the new entry with no permissions */
466 new_aip[dst].ai_id = mod_aip->ai_id;
467 new_aip[dst].ai_idtype = mod_aip->ai_idtype;
468 new_aip[dst].ai_mode = 0;
469 num++; /* set num to the size of new_acl */
472 /* apply the permissions mod */
475 case ACL_MODECHG_ADD:
476 new_aip[dst].ai_mode |= mod_aip->ai_mode;
478 case ACL_MODECHG_DEL:
479 new_aip[dst].ai_mode &= ~mod_aip->ai_mode;
481 case ACL_MODECHG_EQL:
482 new_aip[dst].ai_mode = mod_aip->ai_mode;
487 * if the adjusted entry has no permissions, delete it from the list.
488 * For example, this helps in removing entries for users who no longer
489 * exist. EXCEPTION: never remove the world entry.
491 if (new_aip[dst].ai_mode == 0 && dst > 0)
493 memmove((char *) (new_aip + dst),
494 (char *) (new_aip + dst + 1),
495 (num - dst - 1) * sizeof(AclItem));
496 ARR_DIMS(new_acl)[0] = num - 1;
497 ARR_SIZE(new_acl) -= sizeof(AclItem);
504 * aclinsert (exported function)
507 aclinsert(PG_FUNCTION_ARGS)
509 Acl *old_acl = PG_GETARG_ACL_P(0);
510 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
512 PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
516 aclremove(PG_FUNCTION_ARGS)
518 Acl *old_acl = PG_GETARG_ACL_P(0);
519 AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
527 /* These checks for null input should be dead code, but... */
528 if (!old_acl || ACL_NUM(old_acl) < 1)
529 old_acl = makeacl(1);
532 new_acl = makeacl(ACL_NUM(old_acl));
533 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
534 PG_RETURN_ACL_P(new_acl);
537 old_num = ACL_NUM(old_acl);
538 old_aip = ACL_DAT(old_acl);
540 /* Search for the matching entry */
541 for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip + dst); ++dst)
546 /* Not found, so return copy of source ACL */
547 new_acl = makeacl(old_num);
548 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
552 new_num = old_num - 1;
553 new_acl = makeacl(new_num);
554 new_aip = ACL_DAT(new_acl);
557 elog(ERROR, "aclremove: removal of the world ACL??");
559 else if (dst == old_num - 1)
561 memcpy((char *) new_aip,
563 new_num * sizeof(AclItem));
567 memcpy((char *) new_aip,
569 dst * sizeof(AclItem));
570 memcpy((char *) (new_aip + dst),
571 (char *) (old_aip + dst + 1),
572 (new_num - dst) * sizeof(AclItem));
576 PG_RETURN_ACL_P(new_acl);
580 aclcontains(PG_FUNCTION_ARGS)
582 Acl *acl = PG_GETARG_ACL_P(0);
583 AclItem *aip = PG_GETARG_ACLITEM_P(1);
589 aidat = ACL_DAT(acl);
590 for (i = 0; i < num; ++i)
592 /* Note that aclitemeq only considers id, not mode */
593 if (aclitemeq(aip, aidat + i) &&
594 aip->ai_mode == aidat[i].ai_mode)
595 PG_RETURN_BOOL(true);
597 PG_RETURN_BOOL(false);
602 * Parser support routines for ACL-related statements.
604 * XXX CAUTION: these are called from gram.y, which is not allowed to
605 * do any table accesses. Therefore, it is not kosher to do things
606 * like trying to translate usernames to user IDs here. Keep it all
607 * in string form until statement execution time.
612 * make a acl privilege string out of an existing privilege string
613 * and a new privilege
615 * does not add duplicate privileges
618 aclmakepriv(const char *old_privlist, char new_priv)
624 Assert(strlen(old_privlist) <= strlen(ACL_MODE_STR));
625 priv = palloc(strlen(ACL_MODE_STR)+1);
627 if (old_privlist == NULL || old_privlist[0] == '\0')
634 strcpy(priv, old_privlist);
636 l = strlen(old_privlist);
638 if (l == strlen(ACL_MODE_STR))
639 { /* can't add any more privileges */
643 /* check to see if the new privilege is already in the old string */
644 for (i = 0; i < l; i++)
646 if (priv[i] == new_priv)
650 { /* we really have a new privilege */
660 * user_type must be "A" - all users
664 * Just concatenates the two strings together with a space in between.
665 * Per above comments, we can't try to resolve a user or group name here.
668 aclmakeuser(const char *user_type, const char *user)
672 user_list = palloc(strlen(user_type) + strlen(user) + 2);
673 sprintf(user_list, "%s %s", user_type, user);
679 * makeAclString: We take in the privileges and grantee as well as a
680 * single character '+' or '-' to indicate grant or revoke.
682 * We convert the information to the same external form recognized by
683 * aclitemin (see aclparse) and return that string. Conversion to
684 * internal form happens when the statement is executed.
687 makeAclString(const char *privileges, const char *grantee, char grant_or_revoke)
692 initStringInfo(&str);
694 /* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */
695 if (grantee[0] == 'G') /* group permissions */
697 appendStringInfo(&str, "%s \"%s\"%c%s",
698 ACL_IDTYPE_GID_KEYWORD,
699 grantee + 2, grant_or_revoke, privileges);
701 else if (grantee[0] == 'U') /* user permission */
703 appendStringInfo(&str, "%s \"%s\"%c%s",
704 ACL_IDTYPE_UID_KEYWORD,
705 grantee + 2, grant_or_revoke, privileges);
710 appendStringInfo(&str, "%c%s",
711 grant_or_revoke, privileges);
713 ret = pstrdup(str.data);
720 * has_table_privilege_name_name
721 * Check user privileges on a relation given
722 * name usename, name relname, and text priv name.
726 * 't' indicating user has the privilege
727 * 'f' indicating user does not have the privilege
730 has_table_privilege_name_name(PG_FUNCTION_ARGS)
732 Name username = PG_GETARG_NAME(0);
733 Name relname = PG_GETARG_NAME(1);
734 text *priv_type_text = PG_GETARG_TEXT_P(2);
737 result = has_table_privilege_cname_cname(NameStr(*username),
741 PG_RETURN_BOOL(result);
746 * has_table_privilege_name
747 * Check user privileges on a relation given
748 * name relname and text priv name.
749 * current_user is assumed
753 * 't' indicating user has the privilege
754 * 'f' indicating user does not have the privilege
757 has_table_privilege_name(PG_FUNCTION_ARGS)
759 Name relname = PG_GETARG_NAME(0);
760 text *priv_type_text = PG_GETARG_TEXT_P(1);
764 usesysid = GetUserId();
766 result = has_table_privilege_id_cname(usesysid,
770 PG_RETURN_BOOL(result);
775 * has_table_privilege_name_id
776 * Check user privileges on a relation given
777 * name usename, rel oid, and text priv name.
781 * 't' indicating user has the privilege
782 * 'f' indicating user does not have the privilege
785 has_table_privilege_name_id(PG_FUNCTION_ARGS)
787 Name username = PG_GETARG_NAME(0);
788 Oid reloid = PG_GETARG_OID(1);
789 text *priv_type_text = PG_GETARG_TEXT_P(2);
792 result = has_table_privilege_cname_id(NameStr(*username),
796 PG_RETURN_BOOL(result);
801 * has_table_privilege_id
802 * Check user privileges on a relation given
803 * rel oid, and text priv name.
804 * current_user is assumed
808 * 't' indicating user has the privilege
809 * 'f' indicating user does not have the privilege
812 has_table_privilege_id(PG_FUNCTION_ARGS)
814 Oid reloid = PG_GETARG_OID(0);
815 text *priv_type_text = PG_GETARG_TEXT_P(1);
821 usesysid = GetUserId();
824 * Lookup relname based on rel oid
826 relname = get_rel_name(reloid);
828 elog(ERROR, "has_table_privilege: invalid relation oid %u",
832 * Convert priv_type_text to an AclMode
834 mode = convert_priv_string(priv_type_text);
837 * Finally, check for the privilege
839 result = pg_aclcheck(relname, usesysid, mode);
841 if (result == ACLCHECK_OK)
842 PG_RETURN_BOOL(true);
844 PG_RETURN_BOOL(false);
849 * has_table_privilege_id_name
850 * Check user privileges on a relation given
851 * usesysid, name relname, and priv name.
855 * 't' indicating user has the privilege
856 * 'f' indicating user does not have the privilege
859 has_table_privilege_id_name(PG_FUNCTION_ARGS)
861 int32 usesysid = PG_GETARG_INT32(0);
862 Name relname = PG_GETARG_NAME(1);
863 text *priv_type_text = PG_GETARG_TEXT_P(2);
866 result = has_table_privilege_id_cname(usesysid,
870 PG_RETURN_BOOL(result);
875 * has_table_privilege_id_id
876 * Check user privileges on a relation given
877 * usesysid, rel oid, and priv name.
881 * 't' indicating user has the privilege
882 * 'f' indicating user does not have the privilege
885 has_table_privilege_id_id(PG_FUNCTION_ARGS)
887 int32 usesysid = PG_GETARG_INT32(0);
888 Oid reloid = PG_GETARG_OID(1);
889 text *priv_type_text = PG_GETARG_TEXT_P(2);
895 * Lookup relname based on rel oid
897 relname = get_rel_name(reloid);
899 elog(ERROR, "has_table_privilege: invalid relation oid %u",
903 * Convert priv_type_text to an AclMode
905 mode = convert_priv_string(priv_type_text);
908 * Finally, check for the privilege
910 result = pg_aclcheck(relname, usesysid, mode);
912 if (result == ACLCHECK_OK)
913 PG_RETURN_BOOL(true);
915 PG_RETURN_BOOL(false);
919 * Internal functions.
923 * convert_priv_string
925 * Return mode from priv_type string
932 convert_priv_string(text *priv_type_text)
934 char *priv_type = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(priv_type_text)));
937 * Return mode from priv_type string
939 if (strcasecmp(priv_type, "SELECT") == 0)
942 if (strcasecmp(priv_type, "INSERT") == 0)
945 if (strcasecmp(priv_type, "UPDATE") == 0)
948 if (strcasecmp(priv_type, "DELETE") == 0)
951 if (strcasecmp(priv_type, "RULE") == 0)
954 if (strcasecmp(priv_type, "REFERENCES") == 0)
955 return ACL_REFERENCES;
957 if (strcasecmp(priv_type, "TRIGGER") == 0)
960 elog(ERROR, "has_table_privilege: invalid privilege type %s", priv_type);
962 * We should never get here, but stop the compiler from complaining
968 * has_table_privilege_cname_cname
969 * Check user privileges on a relation given
970 * char *usename, char *relname, and text priv name.
974 * 't' indicating user has the privilege
975 * 'f' indicating user does not have the privilege
978 has_table_privilege_cname_cname(char *username, char *relname,
979 text *priv_type_text)
984 * Lookup userid based on username
986 usesysid = get_usesysid(username);
989 * Make use of has_table_privilege_id_cname.
990 * It accepts the arguments we now have.
992 return has_table_privilege_id_cname(usesysid, relname, priv_type_text);
997 * has_table_privilege_cname_id
998 * Check user privileges on a relation given
999 * char *usename, rel oid, and text priv name.
1003 * 't' indicating user has the privilege
1004 * 'f' indicating user does not have the privilege
1007 has_table_privilege_cname_id(char *username, Oid reloid,
1008 text *priv_type_text)
1014 * Lookup userid based on username
1016 usesysid = get_usesysid(username);
1019 * Lookup relname based on rel oid
1021 relname = get_rel_name(reloid);
1022 if (relname == NULL)
1023 elog(ERROR, "has_table_privilege: invalid relation oid %u",
1027 * Make use of has_table_privilege_id_cname.
1028 * It accepts the arguments we now have.
1030 return has_table_privilege_id_cname(usesysid, relname, priv_type_text);
1035 * has_table_privilege_id_cname
1036 * Check user privileges on a relation given
1037 * usesysid, char *relname, and text priv name.
1041 * 't' indicating user has the privilege
1042 * 'f' indicating user does not have the privilege
1045 has_table_privilege_id_cname(int32 usesysid, char *relname,
1046 text *priv_type_text)
1053 * Check relname is valid.
1054 * This is needed to deal with the case when usename is a superuser
1055 * in which case pg_aclcheck simply returns ACLCHECK_OK
1056 * without validating relname
1058 tuple = SearchSysCache(RELNAME,
1059 PointerGetDatum(relname),
1061 if (!HeapTupleIsValid(tuple))
1062 elog(ERROR, "has_table_privilege: relation \"%s\" does not exist",
1064 ReleaseSysCache(tuple);
1067 * Convert priv_type_text to an AclMode
1069 mode = convert_priv_string(priv_type_text);
1072 * Finally, check for the privilege
1074 result = pg_aclcheck(relname, usesysid, mode);
1076 if (result == ACLCHECK_OK)