aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/acl.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2004-06-01 21:49:23 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2004-06-01 21:49:23 +0000
commit4b2dafcc0b1a579ef5daaa2728223006d1ff98e9 (patch)
tree92ffc092eb0e4f86dfc61da16426b4c1b40b84f3 /src/backend/utils/adt/acl.c
parentf35e8d843117f9e1b8929a3cce8344baef75a389 (diff)
downloadpostgresql-4b2dafcc0b1a579ef5daaa2728223006d1ff98e9.tar.gz
postgresql-4b2dafcc0b1a579ef5daaa2728223006d1ff98e9.zip
Align GRANT/REVOKE behavior more closely with the SQL spec, per discussion
of bug report #1150. Also, arrange that the object owner's irrevocable grant-option permissions are handled implicitly by the system rather than being listed in the ACL as self-granted rights (which was wrong anyway). I did not take the further step of showing these permissions in an explicit 'granted by _SYSTEM' ACL entry, as that seemed more likely to bollix up existing clients than to do anything really useful. It's still a possible future direction, though.
Diffstat (limited to 'src/backend/utils/adt/acl.c')
-rw-r--r--src/backend/utils/adt/acl.c411
1 files changed, 324 insertions, 87 deletions
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 214bda2245a..d02683245ac 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.104 2004/05/07 00:24:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.105 2004/06/01 21:49:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -17,6 +17,7 @@
#include <ctype.h>
#include "catalog/namespace.h"
+#include "catalog/pg_group.h"
#include "catalog/pg_shadow.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
@@ -35,8 +36,11 @@ 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 Acl *recursive_revoke(Acl *acl, AclId grantee,
- AclMode revoke_privs, DropBehavior behavior);
+static void check_circularity(const Acl *old_acl, const AclItem *mod_aip,
+ AclId ownerid);
+static Acl *recursive_revoke(Acl *acl, AclId grantee, AclMode revoke_privs,
+ AclId ownerid, DropBehavior behavior);
+static bool in_group(AclId uid, AclId gid);
static AclMode convert_priv_string(text *priv_type_text);
@@ -554,10 +558,19 @@ acldefault(GrantObjectType objtype, AclId ownerid)
aip++;
}
+ /*
+ * 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;
- /* owner gets default privileges with grant option */
- ACLITEM_SET_PRIVS_IDTYPE(*aip, owner_default, owner_default,
+ ACLITEM_SET_PRIVS_IDTYPE(*aip, owner_default, ACL_NO_RIGHTS,
ACL_IDTYPE_UID);
return acl;
@@ -565,21 +578,31 @@ acldefault(GrantObjectType objtype, AclId ownerid)
/*
- * Add or replace an item in an ACL array. The result is a modified copy;
- * the input object is not changed.
+ * 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: AclId 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.
*/
Acl *
-aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
- unsigned modechg, DropBehavior behavior)
+aclupdate(const Acl *old_acl, const AclItem *mod_aip,
+ int modechg, AclId ownerid, DropBehavior behavior)
{
Acl *new_acl = NULL;
AclItem *old_aip,
*new_aip = NULL;
- AclMode old_privs,
+ AclMode old_rights,
old_goptions,
- new_privs,
+ new_rights,
new_goptions;
int dst,
num;
@@ -590,10 +613,15 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
if (!mod_aip)
{
new_acl = allocacl(ACL_NUM(old_acl));
- memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
+ memcpy(new_acl, old_acl, ACL_SIZE(old_acl));
return new_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);
@@ -626,44 +654,39 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
/* 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_IDTYPE(new_aip[dst], ACL_NO_RIGHTS, ACL_NO_RIGHTS,
+ ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst],
+ ACL_NO_RIGHTS, ACL_NO_RIGHTS,
ACLITEM_GET_IDTYPE(*mod_aip));
num++; /* set num to the size of new_acl */
}
- old_privs = ACLITEM_GET_PRIVS(new_aip[dst]);
+ old_rights = ACLITEM_GET_RIGHTS(new_aip[dst]);
old_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]);
/* apply the specified permissions change */
switch (modechg)
{
case ACL_MODECHG_ADD:
- ACLITEM_SET_PRIVS(new_aip[dst],
- old_privs | ACLITEM_GET_PRIVS(*mod_aip));
- ACLITEM_SET_GOPTIONS(new_aip[dst],
- old_goptions | ACLITEM_GET_GOPTIONS(*mod_aip));
+ ACLITEM_SET_RIGHTS(new_aip[dst],
+ old_rights | ACLITEM_GET_RIGHTS(*mod_aip));
break;
case ACL_MODECHG_DEL:
- ACLITEM_SET_PRIVS(new_aip[dst],
- old_privs & ~ACLITEM_GET_PRIVS(*mod_aip));
- ACLITEM_SET_GOPTIONS(new_aip[dst],
- old_goptions & ~ACLITEM_GET_GOPTIONS(*mod_aip));
+ ACLITEM_SET_RIGHTS(new_aip[dst],
+ old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip));
break;
case ACL_MODECHG_EQL:
- ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst],
- ACLITEM_GET_PRIVS(*mod_aip),
- ACLITEM_GET_GOPTIONS(*mod_aip),
- ACLITEM_GET_IDTYPE(new_aip[dst]));
+ ACLITEM_SET_RIGHTS(new_aip[dst],
+ ACLITEM_GET_RIGHTS(*mod_aip));
break;
}
- new_privs = ACLITEM_GET_PRIVS(new_aip[dst]);
+ 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_privs == ACL_NO_RIGHTS && new_goptions == ACL_NO_RIGHTS)
+ if (new_rights == ACL_NO_RIGHTS)
{
memmove(new_aip + dst,
new_aip + dst + 1,
@@ -676,40 +699,143 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
* Remove abandoned privileges (cascading revoke). Currently we
* can only handle this when the grantee is a user.
*/
- if ((old_goptions & ~new_goptions) != 0
- && ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID)
+ if ((old_goptions & ~new_goptions) != 0)
+ {
+ Assert(ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID);
new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee,
(old_goptions & ~new_goptions),
- behavior);
+ ownerid, behavior);
+ }
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,
+ AclId ownerid)
+{
+ Acl *acl;
+ AclItem *aip;
+ int i,
+ num;
+ AclMode own_privs;
+
+ /*
+ * For now, grant options can only be granted to users, not groups or
+ * PUBLIC. Otherwise we'd have to work a bit harder here.
+ */
+ Assert(ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID);
+
+ /* 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 (ACLITEM_GET_IDTYPE(aip[i]) == ACL_IDTYPE_UID &&
+ 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: AclId 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,
AclId grantee,
AclMode revoke_privs,
+ AclId ownerid,
DropBehavior behavior)
{
- int i;
+ AclMode still_has;
+ AclItem *aip;
+ int i,
+ num;
+
+ /* 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:
- for (i = 0; i < ACL_NUM(acl); i++)
+ num = ACL_NUM(acl);
+ aip = ACL_DAT(acl);
+ for (i = 0; i < num; i++)
{
- AclItem *aip = ACL_DAT(acl);
-
if (aip[i].ai_grantor == grantee
&& (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0)
{
AclItem mod_acl;
+ Acl *new_acl;
if (behavior == DROP_RESTRICT)
ereport(ERROR,
@@ -724,7 +850,12 @@ restart:
revoke_privs,
ACLITEM_GET_IDTYPE(aip[i]));
- acl = aclinsert3(acl, &mod_acl, ACL_MODECHG_DEL, behavior);
+ new_acl = aclupdate(acl, &mod_acl, ACL_MODECHG_DEL,
+ ownerid, behavior);
+
+ pfree(acl);
+ acl = new_acl;
+
goto restart;
}
}
@@ -734,70 +865,177 @@ restart:
/*
- * aclinsert (exported function)
+ * aclmask --- compute bitmask of all privileges held by userid.
+ *
+ * When 'how' = ACLMASK_ALL, this simply returns the privilege bits
+ * held by the given userid 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
+ * is known true. (This lets us exit soonest in cases where the
+ * caller is only going to test for zero or nonzero result.)
+ *
+ * Usage patterns:
+ *
+ * To see if any of a set of privileges are held:
+ * if (aclmask(acl, userid, ownerid, privs, ACLMASK_ANY) != 0)
+ *
+ * To see if all of a set of privileges are held:
+ * if (aclmask(acl, userid, ownerid, privs, ACLMASK_ALL) == privs)
+ *
+ * To determine exactly which of a set of privileges are held:
+ * heldprivs = aclmask(acl, userid, ownerid, privs, ACLMASK_ALL);
*/
-Datum
-aclinsert(PG_FUNCTION_ARGS)
+AclMode
+aclmask(const Acl *acl, AclId userid, AclId ownerid,
+ AclMode mask, AclMaskHow how)
{
- Acl *old_acl = PG_GETARG_ACL_P(0);
- AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
+ AclMode result;
+ AclMode remaining;
+ AclItem *aidat;
+ int i,
+ num;
- PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL, DROP_CASCADE));
-}
+ /*
+ * Null ACL should not happen, since caller should have inserted
+ * appropriate default
+ */
+ if (acl == NULL)
+ elog(ERROR, "null ACL");
-Datum
-aclremove(PG_FUNCTION_ARGS)
-{
- Acl *old_acl = PG_GETARG_ACL_P(0);
- AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
- Acl *new_acl;
- AclItem *old_aip,
- *new_aip;
- int dst,
- old_num,
- new_num;
+ /* Quick exit for mask == 0 */
+ if (mask == 0)
+ return 0;
- /* These checks for null input should be dead code, but... */
- if (!old_acl || ACL_NUM(old_acl) < 0)
- old_acl = allocacl(0);
- if (!mod_aip)
+ result = 0;
+
+ /* Owner always implicitly has all grant options */
+ if (userid == ownerid)
{
- new_acl = allocacl(ACL_NUM(old_acl));
- memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
- PG_RETURN_ACL_P(new_acl);
+ result = mask & ACLITEM_ALL_GOPTION_BITS;
+ if (result == mask)
+ return result;
}
- old_num = ACL_NUM(old_acl);
- old_aip = ACL_DAT(old_acl);
+ num = ACL_NUM(acl);
+ aidat = ACL_DAT(acl);
+
+ /*
+ * Check privileges granted directly to user or to public
+ */
+ for (i = 0; i < num; i++)
+ {
+ AclItem *aidata = &aidat[i];
- /* Search for the matching entry */
- for (dst = 0;
- dst < old_num && !aclitem_match(mod_aip, old_aip + dst);
- ++dst)
- /* continue */ ;
+ if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_WORLD
+ || (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_UID
+ && aidata->ai_grantee == userid))
+ {
+ result |= (aidata->ai_privs & mask);
+ if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+ return result;
+ }
+ }
- if (dst >= old_num)
+ /*
+ * Check privileges granted via groups. We do this in a separate
+ * pass to minimize expensive lookups in pg_group.
+ */
+ remaining = (mask & ~result);
+ for (i = 0; i < num; i++)
{
- /* Not found, so return copy of source ACL */
- new_acl = allocacl(old_num);
- memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
+ AclItem *aidata = &aidat[i];
+
+ if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_GID
+ && (aidata->ai_privs & remaining)
+ && in_group(userid, aidata->ai_grantee))
+ {
+ result |= (aidata->ai_privs & mask);
+ if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+ return result;
+ remaining = (mask & ~result);
+ }
}
- else
+
+ return result;
+}
+
+
+/*
+ * Is user a member of group?
+ */
+static bool
+in_group(AclId uid, AclId gid)
+{
+ bool result = false;
+ HeapTuple tuple;
+ Datum att;
+ bool isNull;
+ IdList *glist;
+ AclId *aidp;
+ int i,
+ num;
+
+ tuple = SearchSysCache(GROSYSID,
+ ObjectIdGetDatum(gid),
+ 0, 0, 0);
+ if (HeapTupleIsValid(tuple))
{
- new_num = old_num - 1;
- new_acl = allocacl(new_num);
- new_aip = ACL_DAT(new_acl);
- if (dst > 0)
- memcpy((char *) new_aip,
- (char *) old_aip,
- dst * sizeof(AclItem));
- if (dst < new_num)
- memcpy((char *) (new_aip + dst),
- (char *) (old_aip + dst + 1),
- (new_num - dst) * sizeof(AclItem));
+ att = SysCacheGetAttr(GROSYSID,
+ tuple,
+ Anum_pg_group_grolist,
+ &isNull);
+ if (!isNull)
+ {
+ /* be sure the IdList is not toasted */
+ glist = DatumGetIdListP(att);
+ /* scan it */
+ num = IDLIST_NUM(glist);
+ aidp = IDLIST_DAT(glist);
+ for (i = 0; i < num; ++i)
+ {
+ if (aidp[i] == uid)
+ {
+ result = true;
+ break;
+ }
+ }
+ /* if IdList was toasted, free detoasted copy */
+ if ((Pointer) glist != DatumGetPointer(att))
+ pfree(glist);
+ }
+ ReleaseSysCache(tuple);
}
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("group with ID %u does not exist", gid)));
+ return result;
+}
+
+
+/*
+ * aclinsert (exported function)
+ */
+Datum
+aclinsert(PG_FUNCTION_ARGS)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("aclinsert is no longer supported")));
+
+ PG_RETURN_NULL(); /* keep compiler quiet */
+}
+
+Datum
+aclremove(PG_FUNCTION_ARGS)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("aclremove is no longer supported")));
- PG_RETURN_ACL_P(new_acl);
+ PG_RETURN_NULL(); /* keep compiler quiet */
}
Datum
@@ -816,8 +1054,7 @@ aclcontains(PG_FUNCTION_ARGS)
if (aip->ai_grantee == aidat[i].ai_grantee
&& ACLITEM_GET_IDTYPE(*aip) == ACLITEM_GET_IDTYPE(aidat[i])
&& aip->ai_grantor == aidat[i].ai_grantor
- && (ACLITEM_GET_PRIVS(*aip) & ACLITEM_GET_PRIVS(aidat[i])) == ACLITEM_GET_PRIVS(*aip)
- && (ACLITEM_GET_GOPTIONS(*aip) & ACLITEM_GET_GOPTIONS(aidat[i])) == ACLITEM_GET_GOPTIONS(*aip))
+ && (ACLITEM_GET_RIGHTS(*aip) & ACLITEM_GET_RIGHTS(aidat[i])) == ACLITEM_GET_RIGHTS(*aip))
PG_RETURN_BOOL(true);
}
PG_RETURN_BOOL(false);