📄 acl.c
字号:
/*------------------------------------------------------------------------- * * acl.c * Basic access control list data structures manipulation routines. * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.127 2005/11/04 17:25:15 tgl Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include <ctype.h>#include "catalog/namespace.h"#include "catalog/pg_authid.h"#include "catalog/pg_auth_members.h"#include "catalog/pg_type.h"#include "commands/dbcommands.h"#include "commands/tablespace.h"#include "miscadmin.h"#include "utils/acl.h"#include "utils/builtins.h"#include "utils/catcache.h"#include "utils/inval.h"#include "utils/lsyscache.h"#include "utils/memutils.h"#include "utils/syscache.h"/* * We frequently need to test whether a given role is a member of some other * role. In most of these tests the "given role" is the same, namely the * active current user. So we can optimize it by keeping a cached list of * all the roles the "given role" is a member of, directly or indirectly. * The cache is flushed whenever we detect a change in pg_auth_members. * * There are actually two caches, one computed under "has_privs" rules * (do not recurse where rolinherit isn't true) and one computed under * "is_member" rules (recurse regardless of rolinherit). * * Possibly this mechanism should be generalized to allow caching membership * info for multiple roles? * * The has_privs cache is: * cached_privs_role is the role OID the cache is for. * cached_privs_roles is an OID list of roles that cached_privs_role * has the privileges of (always including itself). * The cache is valid if cached_privs_role is not InvalidOid. * * The is_member cache is similarly: * cached_member_role is the role OID the cache is for. * cached_membership_roles is an OID list of roles that cached_member_role * is a member of (always including itself). * The cache is valid if cached_member_role is not InvalidOid. */static Oid cached_privs_role = InvalidOid;static List *cached_privs_roles = NIL;static Oid cached_member_role = InvalidOid;static List *cached_membership_roles = NIL;static const char *getid(const char *s, char *n);static void putid(char *p, const char *s);static Acl *allocacl(int n);static const char *aclparse(const char *s, AclItem *aip);static bool aclitem_match(const AclItem *a1, const AclItem *a2);static void check_circularity(const Acl *old_acl, const AclItem *mod_aip, Oid ownerId);static Acl *recursive_revoke(Acl *acl, Oid grantee, AclMode revoke_privs, Oid ownerId, DropBehavior behavior);static int oidComparator(const void *arg1, const void *arg2);static AclMode convert_priv_string(text *priv_type_text);static Oid convert_table_name(text *tablename);static AclMode convert_table_priv_string(text *priv_type_text);static Oid convert_database_name(text *databasename);static AclMode convert_database_priv_string(text *priv_type_text);static Oid convert_function_name(text *functionname);static AclMode convert_function_priv_string(text *priv_type_text);static Oid convert_language_name(text *languagename);static AclMode convert_language_priv_string(text *priv_type_text);static Oid convert_schema_name(text *schemaname);static AclMode convert_schema_priv_string(text *priv_type_text);static Oid convert_tablespace_name(text *tablespacename);static AclMode convert_tablespace_priv_string(text *priv_type_text);static AclMode convert_role_priv_string(text *priv_type_text);static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);static void RoleMembershipCacheCallback(Datum arg, Oid relid);/* * getid * Consumes the first alphanumeric string (identifier) found in string * 's', ignoring any leading white space. If it finds a double quote * it returns the word inside the quotes. * * RETURNS: * the string position in 's' that points to the next non-space character * in 's', after any quotes. Also: * - loads the identifier into 'n'. (If no identifier is found, 'n' * contains an empty string.) 'n' must be NAMEDATALEN bytes. */static const char *getid(const char *s, char *n){ int len = 0; bool in_quotes = false; Assert(s && n); while (isspace((unsigned char) *s)) s++; /* This code had better match what putid() does, below */ for (; *s != '\0' && (isalnum((unsigned char) *s) || *s == '_' || *s == '"' || in_quotes); s++) { if (*s == '"') { /* safe to look at next char (could be '\0' though) */ if (*(s + 1) != '"') { in_quotes = !in_quotes; continue; } /* it's an escaped double quote; skip the escaping char */ s++; } /* Add the character to the string */ if (len >= NAMEDATALEN - 1) ereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG), errmsg("identifier too long"), errdetail("Identifier must be less than %d characters.", NAMEDATALEN))); n[len++] = *s; } n[len] = '\0'; while (isspace((unsigned char) *s)) s++; return s;}/* * Write a role name at *p, adding double quotes if needed. * There must be at least (2*NAMEDATALEN)+2 bytes available at *p. * This needs to be kept in sync with copyAclUserName in pg_dump/dumputils.c */static voidputid(char *p, const char *s){ const char *src; bool safe = true; for (src = s; *src; src++) { /* This test had better match what getid() does, above */ if (!isalnum((unsigned char) *src) && *src != '_') { safe = false; break; } } if (!safe) *p++ = '"'; for (src = s; *src; src++) { /* A double quote character in a username is encoded as "" */ if (*src == '"') *p++ = '"'; *p++ = *src; } if (!safe) *p++ = '"'; *p = '\0';}/* * aclparse * Consumes and parses an ACL specification of the form: * [group|user] [A-Za-z0-9]*=[rwaR]* * from string 's', ignoring any leading white space or white space * between the optional id type keyword (group|user) and the actual * ACL specification. * * The group|user decoration is unnecessary in the roles world, * but we still accept it for backward compatibility. * * This routine is called by the parser as well as aclitemin(), hence * the added generality. * * RETURNS: * the string position in 's' immediately following the ACL * specification. Also: * - loads the structure pointed to by 'aip' with the appropriate * UID/GID, id type identifier and mode type values. */static const char *aclparse(const char *s, AclItem *aip){ AclMode privs, goption, read; char name[NAMEDATALEN]; char name2[NAMEDATALEN]; Assert(s && aip);#ifdef ACLDEBUG elog(LOG, "aclparse: input = \"%s\"", s);#endif s = getid(s, name); if (*s != '=') { /* we just read a keyword, not a name */ if (strcmp(name, "group") != 0 && strcmp(name, "user") != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("unrecognized key word: \"%s\"", name), errhint("ACL key word must be \"group\" or \"user\"."))); s = getid(s, name); /* move s to the name beyond the keyword */ if (name[0] == '\0') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("missing name"), errhint("A name must follow the \"group\" or \"user\" key word."))); } if (*s != '=') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("missing \"=\" sign"))); privs = goption = ACL_NO_RIGHTS; for (++s, read = 0; isalpha((unsigned char) *s) || *s == '*'; s++) { switch (*s) { case '*': goption |= read; break; case ACL_INSERT_CHR: read = ACL_INSERT; break; case ACL_SELECT_CHR: read = ACL_SELECT; break; case ACL_UPDATE_CHR: read = ACL_UPDATE; break; case ACL_DELETE_CHR: read = ACL_DELETE; break; case ACL_RULE_CHR: read = ACL_RULE; break; case ACL_REFERENCES_CHR: read = ACL_REFERENCES; break; case ACL_TRIGGER_CHR: read = ACL_TRIGGER; break; case ACL_EXECUTE_CHR: read = ACL_EXECUTE; break; case ACL_USAGE_CHR: read = ACL_USAGE; break; case ACL_CREATE_CHR: read = ACL_CREATE; break; case ACL_CREATE_TEMP_CHR: read = ACL_CREATE_TEMP; break; default: ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid mode character: must be one of \"%s\"", ACL_ALL_RIGHTS_STR))); } privs |= read; } if (name[0] == '\0') aip->ai_grantee = ACL_ID_PUBLIC; else aip->ai_grantee = get_roleid_checked(name); /* * XXX Allow a degree of backward compatibility by defaulting the grantor * to the superuser. */ if (*s == '/') { s = getid(s + 1, name2); if (name2[0] == '\0') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("a name must follow the \"/\" sign"))); aip->ai_grantor = get_roleid_checked(name2); } else { aip->ai_grantor = BOOTSTRAP_SUPERUSERID; ereport(WARNING, (errcode(ERRCODE_INVALID_GRANTOR), errmsg("defaulting grantor to user ID %u", BOOTSTRAP_SUPERUSERID))); } ACLITEM_SET_PRIVS_GOPTIONS(*aip, privs, goption);#ifdef ACLDEBUG elog(LOG, "aclparse: correctly read [%u %x %x]", aip->ai_grantee, privs, goption);#endif return s;}/* * allocacl * Allocates storage for a new Acl with 'n' entries. * * RETURNS: * the new Acl */static Acl *allocacl(int n){ Acl *new_acl; Size size; if (n < 0) elog(ERROR, "invalid size: %d", n); size = ACL_N_SIZE(n); new_acl = (Acl *) palloc0(size); new_acl->size = size; new_acl->ndim = 1; new_acl->flags = 0; new_acl->elemtype = ACLITEMOID; ARR_LBOUND(new_acl)[0] = 1; ARR_DIMS(new_acl)[0] = n; return new_acl;}/* * aclitemin * Allocates storage for, and fills in, a new AclItem given a string * 's' that contains an ACL specification. See aclparse for details. * * RETURNS: * the new AclItem */Datumaclitemin(PG_FUNCTION_ARGS){ const char *s = PG_GETARG_CSTRING(0); AclItem *aip; aip = (AclItem *) palloc(sizeof(AclItem)); s = aclparse(s, aip); while (isspace((unsigned char) *s)) ++s; if (*s) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("extra garbage at the end of the ACL specification"))); PG_RETURN_ACLITEM_P(aip);}/* * aclitemout * Allocates storage for, and fills in, a new null-delimited string * containing a formatted ACL specification. See aclparse for details. * * RETURNS: * the new string */Datumaclitemout(PG_FUNCTION_ARGS){ AclItem *aip = PG_GETARG_ACLITEM_P(0); char *p; char *out; HeapTuple htup; unsigned i; out = palloc(strlen("=/") + 2 * N_ACL_RIGHTS + 2 * (2 * NAMEDATALEN + 2) + 1); p = out; *p = '\0'; if (aip->ai_grantee != ACL_ID_PUBLIC) { htup = SearchSysCache(AUTHOID, ObjectIdGetDatum(aip->ai_grantee), 0, 0, 0); if (HeapTupleIsValid(htup)) { putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname)); ReleaseSysCache(htup); } else { /* Generate numeric OID if we don't find an entry */ sprintf(p, "%u", aip->ai_grantee); } } while (*p) ++p; *p++ = '='; for (i = 0; i < N_ACL_RIGHTS; ++i) { if (ACLITEM_GET_PRIVS(*aip) & (1 << i)) *p++ = ACL_ALL_RIGHTS_STR[i]; if (ACLITEM_GET_GOPTIONS(*aip) & (1 << i)) *p++ = '*'; } *p++ = '/'; *p = '\0'; htup = SearchSysCache(AUTHOID, ObjectIdGetDatum(aip->ai_grantor), 0, 0, 0); if (HeapTupleIsValid(htup)) { putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname)); ReleaseSysCache(htup); } else { /* Generate numeric OID if we don't find an entry */ sprintf(p, "%u", aip->ai_grantor); } PG_RETURN_CSTRING(out);}/* * aclitem_match * Two AclItems are considered to match iff they have the same * grantee and grantor; the privileges are ignored. */static boolaclitem_match(const AclItem *a1, const AclItem *a2){ return a1->ai_grantee == a2->ai_grantee && a1->ai_grantor == a2->ai_grantor;}/* * aclitem equality operator */Datumaclitem_eq(PG_FUNCTION_ARGS){ AclItem *a1 = PG_GETARG_ACLITEM_P(0); AclItem *a2 = PG_GETARG_ACLITEM_P(1); bool result; result = a1->ai_privs == a2->ai_privs && a1->ai_grantee == a2->ai_grantee && a1->ai_grantor == a2->ai_grantor; PG_RETURN_BOOL(result);}/* * aclitem hash function * * We make aclitems hashable not so much because anyone is likely to hash * them, as because we want array equality to work on aclitem arrays, and * with the typcache mechanism we must have a hash or btree opclass. */Datumhash_aclitem(PG_FUNCTION_ARGS){ AclItem *a = PG_GETARG_ACLITEM_P(0); /* not very bright, but avoids any issue of padding in struct */ PG_RETURN_UINT32((uint32) (a->ai_privs + a->ai_grantee + a->ai_grantor));}/* * acldefault() --- create an ACL describing default access permissions * * Change this routine if you want to alter the default access policy for * newly-created objects (or any object with a NULL acl entry).
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -