Newer
Older
/*-------------------------------------------------------------------------
*
* 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.130 2006/01/21 02:16:19 momjian 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"
Tom Lane
committed
#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.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 Oid cached_member_role = InvalidOid;
static List *cached_membership_roles = NIL;
Peter Eisentraut
committed
static const char *getid(const char *s, char *n);
static void putid(char *p, const char *s);
static Acl *allocacl(int n);
static void check_acl(const Acl *acl);
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);
Tom Lane
committed
static AclMode convert_table_priv_string(text *priv_type_text);
Tom Lane
committed
static AclMode convert_database_priv_string(text *priv_type_text);
Tom Lane
committed
static AclMode convert_function_priv_string(text *priv_type_text);
Tom Lane
committed
static AclMode convert_language_priv_string(text *priv_type_text);
Tom Lane
committed
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);
* 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.
* 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.
Peter Eisentraut
committed
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;
}
while (isspace((unsigned char) *s))
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 void
putid(char *p, const char *s)
{
const char *src;
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';
}
* 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.
* 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)
Bruce Momjian
committed
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),
Peter Eisentraut
committed
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;
Bruce Momjian
committed
break;
case ACL_RULE_CHR:
read = ACL_RULE;
Bruce Momjian
committed
break;
case ACL_REFERENCES_CHR:
read = ACL_REFERENCES;
Bruce Momjian
committed
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;
Bruce Momjian
committed
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);
* Allocates storage for a new Acl with 'n' entries.
static Acl *
Bruce Momjian
committed
Acl *new_acl;
Size size;
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->dataoffset = 0; /* we never put in any nulls */
new_acl->elemtype = ACLITEMOID;
ARR_LBOUND(new_acl)[0] = 1;
ARR_DIMS(new_acl)[0] = n;
/*
* Verify that an ACL array is acceptable (one-dimensional and has no nulls)
*/
static void
check_acl(const Acl *acl)
{
if (ARR_ELEMTYPE(acl) != ACLITEMOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ACL array contains wrong datatype")));
if (ARR_NDIM(acl) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ACL arrays must be one-dimensional")));
if (ARR_HASNULL(acl))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("ACL arrays must not contain nulls")));
}
* Allocates storage for, and fills in, a new AclItem given a string
* 's' that contains an ACL specification. See aclparse for details.
* the new AclItem
Datum
aclitemin(PG_FUNCTION_ARGS)
Peter Eisentraut
committed
const char *s = PG_GETARG_CSTRING(0);
Bruce Momjian
committed
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.
* the new string
Datum
aclitemout(PG_FUNCTION_ARGS)
char *p;
Bruce Momjian
committed
char *out;
HeapTuple htup;
Bruce Momjian
committed
unsigned i;
out = palloc(strlen("=/") +
2 * N_ACL_RIGHTS +
2 * (2 * NAMEDATALEN + 2) +
1);
p = out;
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;
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 bool
aclitem_match(const AclItem *a1, const AclItem *a2)
return a1->ai_grantee == a2->ai_grantee &&
a1->ai_grantor == a2->ai_grantor;
/*
* aclitem equality operator
*/
Datum
aclitem_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.
*/
Datum
hash_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).
acldefault(GrantObjectType objtype, Oid ownerId)
AclMode world_default;
AclMode owner_default;
Bruce Momjian
committed
Acl *acl;
AclItem *aip;
switch (objtype)
{
case ACL_OBJECT_RELATION:
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_RELATION;
break;
case ACL_OBJECT_SEQUENCE:
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_SEQUENCE;
break;
case ACL_OBJECT_DATABASE:
owner_default = ACL_ALL_RIGHTS_DATABASE;
break;
case ACL_OBJECT_FUNCTION:
/* Grant EXECUTE by default, for now */
world_default = ACL_EXECUTE;
owner_default = ACL_ALL_RIGHTS_FUNCTION;
break;
case ACL_OBJECT_LANGUAGE:
/* Grant USAGE by default, for now */
world_default = ACL_USAGE;
owner_default = ACL_ALL_RIGHTS_LANGUAGE;
break;
case ACL_OBJECT_NAMESPACE:
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_NAMESPACE;
break;
case ACL_OBJECT_TABLESPACE:
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_TABLESPACE;
break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
owner_default = ACL_NO_RIGHTS;
break;
}
acl = allocacl((world_default != ACL_NO_RIGHTS) ? 2 : 1);
aip = ACL_DAT(acl);
if (world_default != ACL_NO_RIGHTS)
{
aip->ai_grantee = ACL_ID_PUBLIC;
aip->ai_grantor = ownerId;
ACLITEM_SET_PRIVS_GOPTIONS(*aip, world_default, ACL_NO_RIGHTS);
}
/*
* Note that the owner's entry shows all ordinary privileges but no grant
* options. This is because his grant options come "from the system" and
* not from his own efforts. (The SQL spec says that the owner's rights
* come from a "_SYSTEM" authid.) However, we do consider that the
* owner's ordinary privileges are self-granted; this lets him revoke
* them. We implement the owner's grant options without any explicit
* "_SYSTEM"-like ACL entry, by internally special-casing the owner
* whereever we are testing grant options.
*/
aip->ai_grantee = ownerId;
aip->ai_grantor = ownerId;
ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS);
* Update an ACL array to add or remove specified privileges.
*
* old_acl: the input ACL array
* mod_aip: defines the privileges to be added, removed, or substituted
* modechg: ACL_MODECHG_ADD, ACL_MODECHG_DEL, or ACL_MODECHG_EQL
* ownerId: Oid of object owner
* behavior: RESTRICT or CASCADE behavior for recursive removal
*
* ownerid and behavior are only relevant when the update operation specifies
* deletion of grant options.
*
* The result is a modified copy; the input object is not changed.
*
* NB: caller is responsible for having detoasted the input ACL, if needed.
*/
aclupdate(const Acl *old_acl, const AclItem *mod_aip,
int modechg, Oid ownerId, DropBehavior behavior)
Bruce Momjian
committed
AclItem *old_aip,
AclMode old_rights,
old_goptions,
new_rights,
new_goptions;
int dst,
Bruce Momjian
committed
num;
/* Caller probably already checked old_acl, but be safe */
check_acl(old_acl);
/* If granting grant options, check for circularity */
if (modechg != ACL_MODECHG_DEL &&
ACLITEM_GET_GOPTIONS(*mod_aip) != ACL_NO_RIGHTS)
check_circularity(old_acl, mod_aip, ownerId);
num = ACL_NUM(old_acl);
old_aip = ACL_DAT(old_acl);
/*
* Search the ACL for an existing entry for this grantee and grantor. If
* one exists, just modify the entry in-place (well, in the same position,
* since we actually return a copy); otherwise, insert the new entry at
* the end.
for (dst = 0; dst < num; ++dst)
if (aclitem_match(mod_aip, old_aip + dst))
{
/* found a match, so modify existing item */
new_acl = allocacl(num);
new_aip = ACL_DAT(new_acl);
memcpy(new_acl, old_acl, ACL_SIZE(old_acl));
break;
}
if (dst == num)
/* need to append a new item */
new_acl = allocacl(num + 1);
new_aip = ACL_DAT(new_acl);
memcpy(new_aip, old_aip, num * sizeof(AclItem));
/* initialize the new entry with no permissions */
new_aip[dst].ai_grantee = mod_aip->ai_grantee;
new_aip[dst].ai_grantor = mod_aip->ai_grantor;
ACLITEM_SET_PRIVS_GOPTIONS(new_aip[dst],
ACL_NO_RIGHTS, ACL_NO_RIGHTS);
num++; /* set num to the size of new_acl */
}
old_rights = ACLITEM_GET_RIGHTS(new_aip[dst]);
old_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]);
/* apply the specified permissions change */
switch (modechg)
{
Bruce Momjian
committed
case ACL_MODECHG_ADD:
ACLITEM_SET_RIGHTS(new_aip[dst],
old_rights | ACLITEM_GET_RIGHTS(*mod_aip));
Bruce Momjian
committed
break;
case ACL_MODECHG_DEL:
ACLITEM_SET_RIGHTS(new_aip[dst],
old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip));
Bruce Momjian
committed
break;
case ACL_MODECHG_EQL:
ACLITEM_SET_RIGHTS(new_aip[dst],
ACLITEM_GET_RIGHTS(*mod_aip));
Bruce Momjian
committed
break;
new_rights = ACLITEM_GET_RIGHTS(new_aip[dst]);
new_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]);
* If the adjusted entry has no permissions, delete it from the list.
if (new_rights == ACL_NO_RIGHTS)
memmove(new_aip + dst,
new_aip + dst + 1,
(num - dst - 1) * sizeof(AclItem));
ARR_DIMS(new_acl)[0] = num - 1;
ARR_SIZE(new_acl) -= sizeof(AclItem);
/*
* Remove abandoned privileges (cascading revoke). Currently we can only
* handle this when the grantee is not PUBLIC.
*/
if ((old_goptions & ~new_goptions) != 0)
{
Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC);
new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee,
(old_goptions & ~new_goptions),
ownerId, behavior);
/*
* Update an ACL array to reflect a change of owner to the parent object
*
* old_acl: the input ACL array (must not be NULL)
* oldOwnerId: Oid of the old object owner
* newOwnerId: Oid of the new object owner
*
* The result is a modified copy; the input object is not changed.
*
* NB: caller is responsible for having detoasted the input ACL, if needed.
*/
Acl *
aclnewowner(const Acl *old_acl, Oid oldOwnerId, Oid newOwnerId)
{
Acl *new_acl;
AclItem *new_aip;
AclItem *old_aip;
AclItem *dst_aip;
AclItem *src_aip;
AclItem *targ_aip;
bool newpresent = false;
int dst,
src,
targ,
num;
check_acl(old_acl);
/*
* Make a copy of the given ACL, substituting new owner ID for old
* wherever it appears as either grantor or grantee. Also note if the new
* owner ID is already present.
*/
num = ACL_NUM(old_acl);
old_aip = ACL_DAT(old_acl);
new_acl = allocacl(num);
new_aip = ACL_DAT(new_acl);
memcpy(new_aip, old_aip, num * sizeof(AclItem));
for (dst = 0, dst_aip = new_aip; dst < num; dst++, dst_aip++)
{
if (dst_aip->ai_grantor == oldOwnerId)
dst_aip->ai_grantor = newOwnerId;
else if (dst_aip->ai_grantor == newOwnerId)
newpresent = true;
if (dst_aip->ai_grantee == oldOwnerId)
dst_aip->ai_grantee = newOwnerId;
else if (dst_aip->ai_grantee == newOwnerId)
newpresent = true;
}
/*
* If the old ACL contained any references to the new owner, then we may
* now have generated an ACL containing duplicate entries. Find them and
* merge them so that there are not duplicates. (This is relatively
* expensive since we use a stupid O(N^2) algorithm, but it's unlikely to
* be the normal case.)
*
* To simplify deletion of duplicate entries, we temporarily leave them in
* the array but set their privilege masks to zero; when we reach such an
* entry it's just skipped. (Thus, a side effect of this code will be to
* remove privilege-free entries, should there be any in the input.) dst
* is the next output slot, targ is the currently considered input slot
* (always >= dst), and src scans entries to the right of targ looking for
* duplicates. Once an entry has been emitted to dst it is known
* duplicate-free and need not be considered anymore.
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
*/
if (newpresent)
{
dst = 0;
for (targ = 0, targ_aip = new_aip; targ < num; targ++, targ_aip++)
{
/* ignore if deleted in an earlier pass */
if (ACLITEM_GET_RIGHTS(*targ_aip) == ACL_NO_RIGHTS)
continue;
/* find and merge any duplicates */
for (src = targ + 1, src_aip = targ_aip + 1; src < num;
src++, src_aip++)
{
if (ACLITEM_GET_RIGHTS(*src_aip) == ACL_NO_RIGHTS)
continue;
if (aclitem_match(targ_aip, src_aip))
{
ACLITEM_SET_RIGHTS(*targ_aip,
ACLITEM_GET_RIGHTS(*targ_aip) |
ACLITEM_GET_RIGHTS(*src_aip));
/* mark the duplicate deleted */
ACLITEM_SET_RIGHTS(*src_aip, ACL_NO_RIGHTS);
}
}
/* and emit to output */
new_aip[dst] = *targ_aip;
dst++;
}
/* Adjust array size to be 'dst' items */
ARR_DIMS(new_acl)[0] = dst;
ARR_SIZE(new_acl) = ACL_N_SIZE(dst);
}
return new_acl;
}
/*
* When granting grant options, we must disallow attempts to set up circular
* chains of grant options. Suppose A (the object owner) grants B some
* privileges with grant option, and B re-grants them to C. If C could
* grant the privileges to B as well, then A would be unable to effectively
* revoke the privileges from B, since recursive_revoke would consider that
* B still has 'em from C.
*
* We check for this by recursively deleting all grant options belonging to
* the target grantee, and then seeing if the would-be grantor still has the
* grant option or not.
*/
static void
check_circularity(const Acl *old_acl, const AclItem *mod_aip,
Oid ownerId)
{
Acl *acl;
AclItem *aip;
int i,
num;
AclMode own_privs;
check_acl(old_acl);
/*
* For now, grant options can only be granted to roles, not PUBLIC.
* Otherwise we'd have to work a bit harder here.
*/
Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC);
/* The owner always has grant options, no need to check */
if (mod_aip->ai_grantor == ownerId)
return;
/* Make a working copy */
acl = allocacl(ACL_NUM(old_acl));
memcpy(acl, old_acl, ACL_SIZE(old_acl));
/* Zap all grant options of target grantee, plus what depends on 'em */
cc_restart:
num = ACL_NUM(acl);
aip = ACL_DAT(acl);
for (i = 0; i < num; i++)
{
if (aip[i].ai_grantee == mod_aip->ai_grantee &&
ACLITEM_GET_GOPTIONS(aip[i]) != ACL_NO_RIGHTS)
{
Acl *new_acl;
/* We'll actually zap ordinary privs too, but no matter */
new_acl = aclupdate(acl, &aip[i], ACL_MODECHG_DEL,
ownerId, DROP_CASCADE);
pfree(acl);
acl = new_acl;
goto cc_restart;
}
}
/* Now we can compute grantor's independently-derived privileges */
own_privs = aclmask(acl,
mod_aip->ai_grantor,
ownerId,
ACL_GRANT_OPTION_FOR(ACLITEM_GET_GOPTIONS(*mod_aip)),
ACLMASK_ALL);
own_privs = ACL_OPTION_TO_PRIVS(own_privs);
if ((ACLITEM_GET_GOPTIONS(*mod_aip) & ~own_privs) != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("grant options cannot be granted back to your own grantor")));
pfree(acl);
}
/*
* Ensure that no privilege is "abandoned". A privilege is abandoned
* if the user that granted the privilege loses the grant option. (So
* the chain through which it was granted is broken.) Either the
* abandoned privileges are revoked as well, or an error message is
* printed, depending on the drop behavior option.
*
* acl: the input ACL list
* grantee: the user from whom some grant options have been revoked
* revoke_privs: the grant options being revoked
* ownerId: Oid of object owner
* behavior: RESTRICT or CASCADE behavior for recursive removal
*
* The input Acl object is pfree'd if replaced.
*/
static Acl *
recursive_revoke(Acl *acl,
Oid grantee,
AclMode revoke_privs,
Oid ownerId,
DropBehavior behavior)
{
AclMode still_has;
AclItem *aip;
int i,
num;
check_acl(acl);
/* The owner can never truly lose grant options, so short-circuit */
if (grantee == ownerId)
return acl;
/* The grantee might still have the privileges via another grantor */
still_has = aclmask(acl, grantee, ownerId,
ACL_GRANT_OPTION_FOR(revoke_privs),
ACLMASK_ALL);
revoke_privs &= ~still_has;
if (revoke_privs == ACL_NO_RIGHTS)
return acl;
restart:
num = ACL_NUM(acl);
aip = ACL_DAT(acl);
for (i = 0; i < num; i++)
{
if (aip[i].ai_grantor == grantee
&& (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0)
{
Acl *new_acl;
if (behavior == DROP_RESTRICT)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("dependent privileges exist"),
errhint("Use CASCADE to revoke them too.")));
mod_acl.ai_grantor = grantee;
mod_acl.ai_grantee = aip[i].ai_grantee;
ACLITEM_SET_PRIVS_GOPTIONS(mod_acl,
revoke_privs,
revoke_privs);
new_acl = aclupdate(acl, &mod_acl, ACL_MODECHG_DEL,
ownerId, behavior);
pfree(acl);
acl = new_acl;
goto restart;
}
}
return acl;
}
* aclmask --- compute bitmask of all privileges held by roleid.
*
* When 'how' = ACLMASK_ALL, this simply returns the privilege bits
* held by the given roleid according to the given ACL list, ANDed
* with 'mask'. (The point of passing 'mask' is to let the routine
* exit early if all privileges of interest have been found.)
*
* When 'how' = ACLMASK_ANY, returns as soon as any bit in the mask