1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-14 08:21:07 +03:00

Grant options, and cascading revoke. Grant options are allowed only for

users right now, not groups.  Extension of has_foo_privileges functions to
query the grant options.  Extension of aclitem type to store grantor.
This commit is contained in:
Peter Eisentraut
2003-01-23 23:39:07 +00:00
parent aa78ca3a95
commit ef7422510e
18 changed files with 671 additions and 446 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.79 2002/12/04 05:18:31 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.80 2003/01/23 23:38:55 petere Exp $
*
* NOTES
* See acl.h.
@ -47,7 +47,7 @@ static void ExecuteGrantStmt_Namespace(GrantStmt *stmt);
static const char *privilege_to_string(AclMode privilege);
static AclResult aclcheck(Acl *acl, AclId id, uint32 idtype, AclMode mode);
static AclResult aclcheck(Acl *acl, AclId userid, AclMode mode);
#ifdef ACLDEBUG
@ -75,7 +75,8 @@ dumpacl(Acl *acl)
*/
static Acl *
merge_acl_with_grant(Acl *old_acl, bool is_grant,
List *grantees, AclMode privileges)
List *grantees, AclMode privileges,
bool grant_option, DropBehavior behavior)
{
unsigned modechg;
List *j;
@ -96,26 +97,38 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
if (grantee->username)
{
aclitem. ai_id = get_usesysid(grantee->username);
aclitem.ai_grantee = get_usesysid(grantee->username);
idtype = ACL_IDTYPE_UID;
}
else if (grantee->groupname)
{
aclitem. ai_id = get_grosysid(grantee->groupname);
aclitem.ai_grantee = get_grosysid(grantee->groupname);
idtype = ACL_IDTYPE_GID;
}
else
{
aclitem. ai_id = ACL_ID_WORLD;
aclitem.ai_grantee = ACL_ID_WORLD;
idtype = ACL_IDTYPE_WORLD;
}
ACLITEM_SET_PRIVS_IDTYPE(aclitem, privileges, idtype);
/*
* Grant options can only be granted to individual users, not
* groups or public. The reason is that if a user would
* re-grant a privilege that he held through a group having a
* grant option, and later the user is removed from the group,
* the situation is impossible to clean up.
*/
if (is_grant && idtype != ACL_IDTYPE_UID && grant_option)
elog(ERROR, "grant options can only be granted to individual users");
new_acl = aclinsert3(new_acl, &aclitem, modechg);
aclitem.ai_grantor = GetUserId();
ACLITEM_SET_PRIVS_IDTYPE(aclitem,
(is_grant || !grant_option) ? privileges : ACL_NO_RIGHTS,
(grant_option || !is_grant) ? privileges : ACL_NO_RIGHTS,
idtype);
new_acl = aclinsert3(new_acl, &aclitem, modechg, behavior);
#ifdef ACLDEBUG
dumpacl(new_acl);
@ -202,8 +215,10 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
elog(ERROR, "relation %u not found", relOid);
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
if (!pg_class_ownercheck(relOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, relvar->relname);
if (stmt->is_grant
&& !pg_class_ownercheck(relOid, GetUserId())
&& pg_class_aclcheck(relOid, GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
aclcheck_error(ACLCHECK_NO_PRIV, relvar->relname);
if (pg_class_tuple->relkind == RELKIND_INDEX)
elog(ERROR, "\"%s\" is an index",
@ -223,7 +238,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
old_acl = DatumGetAclPCopy(aclDatum);
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grantees, privileges);
stmt->grantees, privileges,
stmt->grant_option, stmt->behavior);
/* finished building new ACL value, now insert it */
MemSet(values, 0, sizeof(values));
@ -298,8 +314,10 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
elog(ERROR, "database \"%s\" not found", dbname);
pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple);
if (!superuser() && pg_database_tuple->datdba != GetUserId())
elog(ERROR, "permission denied");
if (stmt->is_grant
&& pg_database_tuple->datdba != GetUserId()
&& pg_database_aclcheck(HeapTupleGetOid(tuple), GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
aclcheck_error(ACLCHECK_NO_PRIV, NameStr(pg_database_tuple->datname));
/*
* If there's no ACL, create a default.
@ -314,7 +332,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
old_acl = DatumGetAclPCopy(aclDatum);
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grantees, privileges);
stmt->grantees, privileges,
stmt->grant_option, stmt->behavior);
/* finished building new ACL value, now insert it */
MemSet(values, 0, sizeof(values));
@ -389,8 +408,10 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
elog(ERROR, "function %u not found", oid);
pg_proc_tuple = (Form_pg_proc) GETSTRUCT(tuple);
if (!pg_proc_ownercheck(oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER,
if (stmt->is_grant
&& !pg_proc_ownercheck(oid, GetUserId())
&& pg_proc_aclcheck(oid, GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
aclcheck_error(ACLCHECK_NO_PRIV,
NameStr(pg_proc_tuple->proname));
/*
@ -407,7 +428,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
old_acl = DatumGetAclPCopy(aclDatum);
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grantees, privileges);
stmt->grantees, privileges,
stmt->grant_option, stmt->behavior);
/* finished building new ACL value, now insert it */
MemSet(values, 0, sizeof(values));
@ -470,9 +492,6 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
char nulls[Natts_pg_language];
char replaces[Natts_pg_language];
if (!superuser())
elog(ERROR, "permission denied");
relation = heap_openr(LanguageRelationName, RowExclusiveLock);
tuple = SearchSysCache(LANGNAME,
PointerGetDatum(langname),
@ -484,6 +503,11 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
if (!pg_language_tuple->lanpltrusted && stmt->is_grant)
elog(ERROR, "language \"%s\" is not trusted", langname);
if (stmt->is_grant
&& !superuser()
&& pg_language_aclcheck(HeapTupleGetOid(tuple), GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
aclcheck_error(ACLCHECK_NO_PRIV, NameStr(pg_language_tuple->lanname));
/*
* If there's no ACL, create a default.
*/
@ -497,7 +521,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
old_acl = DatumGetAclPCopy(aclDatum);
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grantees, privileges);
stmt->grantees, privileges,
stmt->grant_option, stmt->behavior);
/* finished building new ACL value, now insert it */
MemSet(values, 0, sizeof(values));
@ -568,8 +593,10 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
elog(ERROR, "namespace \"%s\" not found", nspname);
pg_namespace_tuple = (Form_pg_namespace) GETSTRUCT(tuple);
if (!pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, nspname);
if (stmt->is_grant
&& !pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId())
&& pg_namespace_aclcheck(HeapTupleGetOid(tuple), GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
aclcheck_error(ACLCHECK_NO_PRIV, nspname);
/*
* If there's no ACL, create a default using the
@ -586,7 +613,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
old_acl = DatumGetAclPCopy(aclDatum);
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grantees, privileges);
stmt->grantees, privileges,
stmt->grant_option, stmt->behavior);
/* finished building new ACL value, now insert it */
MemSet(values, 0, sizeof(values));
@ -741,125 +769,53 @@ in_group(AclId uid, AclId gid)
/*
* aclcheck
*
* Returns ACLCHECK_OK if the 'id' of type 'idtype' has ACL entries in 'acl'
* to satisfy any one of the requirements of 'mode'. Returns an appropriate
* ACLCHECK_* error code otherwise.
*
* The ACL list is expected to be sorted in standard order.
* Returns ACLCHECK_OK if the 'userid' has ACL entries in 'acl' to
* satisfy any one of the requirements of 'mode'. Returns an
* appropriate ACLCHECK_* error code otherwise.
*/
static AclResult
aclcheck(Acl *acl, AclId id, uint32 idtype, AclMode mode)
aclcheck(Acl *acl, AclId userid, AclMode mode)
{
AclItem *aip,
*aidat;
AclItem *aidat;
int i,
num;
/*
* If ACL is null, default to "OK" --- this should not happen, since
* caller should have inserted appropriate default
* Null ACL should not happen, since caller should have inserted
* appropriate default
*/
if (!acl)
if (acl == NULL)
{
elog(DEBUG1, "aclcheck: null ACL, returning OK");
return ACLCHECK_OK;
elog(ERROR, "aclcheck: internal error -- null ACL");
return ACLCHECK_NO_PRIV;
}
num = ACL_NUM(acl);
aidat = ACL_DAT(acl);
/*
* We'll treat the empty ACL like that, too, although this is more
* like an error (i.e., you manually blew away your ACL array) -- the
* system never creates an empty ACL, since there must always be a
* "world" entry in the first slot.
* See if privilege is granted directly to user or to public
*/
if (num < 1)
{
elog(DEBUG1, "aclcheck: zero-length ACL, returning OK");
return ACLCHECK_OK;
}
for (i = 0; i < num; i++)
if (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_WORLD
|| (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_UID
&& aidat[i].ai_grantee == userid))
{
if (aidat[i].ai_privs & mode)
return ACLCHECK_OK;
}
/*
* "World" rights are applicable regardless of the passed-in ID, and
* since they're much the cheapest to check, check 'em first.
* See if he has the permission via any group (do this in a
* separate pass to avoid expensive(?) lookups in pg_group)
*/
if (ACLITEM_GET_IDTYPE(*aidat) != ACL_IDTYPE_WORLD)
elog(ERROR, "aclcheck: first entry in ACL is not 'world' entry");
if (aidat->ai_privs & mode)
{
#ifdef ACLDEBUG
elog(DEBUG1, "aclcheck: using world=%d", ACLITEM_GET_PRIVS(*aidat));
#endif
return ACLCHECK_OK;
}
for (i = 0; i < num; i++)
if (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_GID
&& aidat[i].ai_privs & mode
&& in_group(userid, aidat[i].ai_grantee))
return ACLCHECK_OK;
switch (idtype)
{
case ACL_IDTYPE_UID:
/* See if permission is granted directly to user */
for (i = 1, aip = aidat + 1; /* skip world entry */
i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_UID;
++i, ++aip)
{
if (aip->ai_id == id)
{
#ifdef ACLDEBUG
elog(DEBUG1, "aclcheck: found user %u/%d",
aip->ai_id, ACLITEM_GET_PRIVS(*aip));
#endif
if (aip->ai_privs & mode)
return ACLCHECK_OK;
}
}
/* See if he has the permission via any group */
for (;
i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_GID;
++i, ++aip)
{
if (aip->ai_privs & mode)
{
if (in_group(id, aip->ai_id))
{
#ifdef ACLDEBUG
elog(DEBUG1, "aclcheck: found group %u/%d",
aip->ai_id, ACLITEM_GET_PRIVS(*aip));
#endif
return ACLCHECK_OK;
}
}
}
break;
case ACL_IDTYPE_GID:
/* Look for this group ID */
for (i = 1, aip = aidat + 1; /* skip world entry */
i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_UID;
++i, ++aip)
/* skip UID entry */ ;
for (;
i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_GID;
++i, ++aip)
{
if (aip->ai_id == id)
{
#ifdef ACLDEBUG
elog(DEBUG1, "aclcheck: found group %u/%d",
aip->ai_id, ACLITEM_GET_PRIVS(*aip));
#endif
if (aip->ai_privs & mode)
return ACLCHECK_OK;
}
}
break;
case ACL_IDTYPE_WORLD:
/* Only check the world entry */
break;
default:
elog(ERROR, "aclcheck: bogus ACL id type: %d", idtype);
break;
}
/* If get here, he doesn't have the privilege nohow */
/* If here, doesn't have the privilege. */
return ACLCHECK_NO_PRIV;
}
@ -976,7 +932,7 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode)
acl = DatumGetAclP(aclDatum);
}
result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode);
result = aclcheck(acl, userid, mode);
/* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
@ -1038,7 +994,7 @@ pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode)
acl = DatumGetAclP(aclDatum);
}
result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode);
result = aclcheck(acl, userid, mode);
/* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
@ -1092,7 +1048,7 @@ pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode)
acl = DatumGetAclP(aclDatum);
}
result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode);
result = aclcheck(acl, userid, mode);
/* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
@ -1142,7 +1098,7 @@ pg_language_aclcheck(Oid lang_oid, AclId userid, AclMode mode)
acl = DatumGetAclP(aclDatum);
}
result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode);
result = aclcheck(acl, userid, mode);
/* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
@ -1202,7 +1158,7 @@ pg_namespace_aclcheck(Oid nsp_oid, AclId userid, AclMode mode)
acl = DatumGetAclP(aclDatum);
}
result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode);
result = aclcheck(acl, userid, mode);
/* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum))

View File

@ -72,7 +72,7 @@ E081 Basic Privileges 04 UPDATE privilege at the table level YES
E081 Basic Privileges 05 UPDATE privilege at the column level NO
E081 Basic Privileges 06 REFERENCES privilege at the table level YES
E081 Basic Privileges 07 REFERENCES privilege at the column level NO
E081 Basic Privileges 08 WITH GRANT OPTION NO
E081 Basic Privileges 08 WITH GRANT OPTION YES
E091 Set functions YES
E091 Set functions 01 AVG YES
E091 Set functions 02 COUNT YES
@ -133,10 +133,10 @@ F031 Basic schema manipulation 16 DROP VIEW statement: RESTRICT clause YES
F031 Basic schema manipulation 19 REVOKE statement: RESTRICT clause YES
F032 CASCADE drop behavior YES
F033 ALTER TABLE statement: DROP COLUMN clause YES
F034 Extended REVOKE statement NO
F034 Extended REVOKE statement 01 REVOKE statement performed by other than the owner of a schema object NO
F034 Extended REVOKE statement 02 REVOKE statement: GRANT OPTION FOR clause NO
F034 Extended REVOKE statement 03 REVOKE statement to revoke a privilege that the grantee has WITH GRANT OPTION NO
F034 Extended REVOKE statement YES
F034 Extended REVOKE statement 01 REVOKE statement performed by other than the owner of a schema object YES
F034 Extended REVOKE statement 02 REVOKE statement: GRANT OPTION FOR clause YES
F034 Extended REVOKE statement 03 REVOKE statement to revoke a privilege that the grantee has WITH GRANT OPTION YES
F041 Basic joined table YES
F041 Basic joined table 01 Inner join (but not necessarily the INNER keyword) YES
F041 Basic joined table 02 INNER keyword YES

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.237 2003/01/20 18:54:46 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.238 2003/01/23 23:38:56 petere Exp $
*
*-------------------------------------------------------------------------
*/
@ -1563,6 +1563,8 @@ _copyGrantStmt(GrantStmt *from)
COPY_NODE_FIELD(objects);
COPY_INTLIST_FIELD(privileges);
COPY_NODE_FIELD(grantees);
COPY_SCALAR_FIELD(grant_option);
COPY_SCALAR_FIELD(behavior);
return newnode;
}

View File

@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.181 2003/01/20 18:54:46 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.182 2003/01/23 23:38:56 petere Exp $
*
*-------------------------------------------------------------------------
*/
@ -635,6 +635,8 @@ _equalGrantStmt(GrantStmt *a, GrantStmt *b)
COMPARE_NODE_FIELD(objects);
COMPARE_INTLIST_FIELD(privileges);
COMPARE_NODE_FIELD(grantees);
COMPARE_SCALAR_FIELD(grant_option);
COMPARE_SCALAR_FIELD(behavior);
return true;
}

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.395 2003/01/10 22:03:27 petere Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.396 2003/01/23 23:38:56 petere Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -163,6 +163,7 @@ static void doNegateFloat(Value *v);
%type <ival> opt_lock lock_type cast_context
%type <boolean> opt_force opt_or_replace transaction_access_mode
opt_grant_grant_option opt_revoke_grant_option
%type <list> user_list
@ -2737,6 +2738,7 @@ GrantStmt: GRANT privileges ON privilege_target TO grantee_list
n->objtype = ($4)->objtype;
n->objects = ($4)->objs;
n->grantees = $6;
n->grant_option = $7;
$$ = (Node*)n;
}
;
@ -2750,9 +2752,8 @@ RevokeStmt: REVOKE opt_revoke_grant_option privileges ON privilege_target
n->objtype = ($5)->objtype;
n->objects = ($5)->objs;
n->grantees = $7;
if ($8 == DROP_CASCADE)
elog(ERROR, "REVOKE ... CASCADE is not implemented");
n->grant_option = $2;
n->behavior = $8;
$$ = (Node *)n;
}
@ -2867,19 +2868,13 @@ grantee: ColId
opt_grant_grant_option:
WITH GRANT OPTION
{
elog(ERROR, "grant options are not implemented");
}
| /*EMPTY*/
WITH GRANT OPTION { $$ = TRUE; }
| /*EMPTY*/ { $$ = FALSE; }
;
opt_revoke_grant_option:
GRANT OPTION FOR
{
elog(ERROR, "grant options are not implemented");
}
| /*EMPTY*/
GRANT OPTION FOR { $$ = TRUE; }
| /*EMPTY*/ { $$ = FALSE; }
;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.84 2002/12/05 04:04:42 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.85 2003/01/23 23:39:01 petere Exp $
*
*-------------------------------------------------------------------------
*/
@ -32,9 +32,10 @@
static const char *getid(const char *s, char *n);
static Acl *makeacl(int n);
static const char *aclparse(const char *s, AclItem *aip, unsigned *modechg);
static const char *aclparse(const char *s, AclItem *aip);
static bool aclitemeq(const AclItem *a1, const AclItem *a2);
static bool aclitemgt(const AclItem *a1, const AclItem *a2);
static Acl *recursive_revoke(Acl *acl, AclId grantee,
AclMode revoke_privs, DropBehavior behavior);
static Oid convert_table_name(text *tablename);
static AclMode convert_table_priv_string(text *priv_type_text);
@ -102,7 +103,7 @@ getid(const char *s, char *n)
/*
* aclparse
* Consumes and parses an ACL specification of the form:
* [group|user] [A-Za-z0-9]*[+-=][rwaR]*
* [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.
@ -115,25 +116,23 @@ getid(const char *s, char *n)
* specification. Also:
* - loads the structure pointed to by 'aip' with the appropriate
* UID/GID, id type identifier and mode type values.
* - loads 'modechg' with the mode change flag.
*/
static const char *
aclparse(const char *s, AclItem *aip, unsigned *modechg)
aclparse(const char *s, AclItem *aip)
{
AclMode privs;
AclMode privs, goption, read;
uint32 idtype;
char name[NAMEDATALEN];
char name2[NAMEDATALEN];
Assert(s && aip && modechg);
Assert(s && aip);
#ifdef ACLDEBUG
elog(LOG, "aclparse: input = '%s'", s);
#endif
idtype = ACL_IDTYPE_UID;
s = getid(s, name);
if (*s != ACL_MODECHG_ADD_CHR &&
*s != ACL_MODECHG_DEL_CHR &&
*s != ACL_MODECHG_EQL_CHR)
if (*s != '=')
{
/* we just read a keyword, not a name */
if (strncmp(name, ACL_IDTYPE_GID_KEYWORD, sizeof(name)) == 0)
@ -147,87 +146,93 @@ aclparse(const char *s, AclItem *aip, unsigned *modechg)
if (name[0] == '\0')
idtype = ACL_IDTYPE_WORLD;
switch (*s)
{
case ACL_MODECHG_ADD_CHR:
*modechg = ACL_MODECHG_ADD;
break;
case ACL_MODECHG_DEL_CHR:
*modechg = ACL_MODECHG_DEL;
break;
case ACL_MODECHG_EQL_CHR:
*modechg = ACL_MODECHG_EQL;
break;
default:
elog(ERROR, "aclparse: mode change flag must use \"%c%c%c\"",
ACL_MODECHG_ADD_CHR,
ACL_MODECHG_DEL_CHR,
ACL_MODECHG_EQL_CHR);
}
if (*s != '=')
elog(ERROR, "aclparse: expecting \"=\" sign");
privs = ACL_NO_RIGHTS;
privs = goption = ACL_NO_RIGHTS;
while (isalpha((unsigned char) *++s))
for (++s, read=0; isalpha((unsigned char) *s) || *s == '*'; s++)
{
switch (*s)
{
case '*':
goption |= read;
break;
case ACL_INSERT_CHR:
privs |= ACL_INSERT;
read = ACL_INSERT;
break;
case ACL_SELECT_CHR:
privs |= ACL_SELECT;
read = ACL_SELECT;
break;
case ACL_UPDATE_CHR:
privs |= ACL_UPDATE;
read = ACL_UPDATE;
break;
case ACL_DELETE_CHR:
privs |= ACL_DELETE;
read = ACL_DELETE;
break;
case ACL_RULE_CHR:
privs |= ACL_RULE;
read = ACL_RULE;
break;
case ACL_REFERENCES_CHR:
privs |= ACL_REFERENCES;
read = ACL_REFERENCES;
break;
case ACL_TRIGGER_CHR:
privs |= ACL_TRIGGER;
read = ACL_TRIGGER;
break;
case ACL_EXECUTE_CHR:
privs |= ACL_EXECUTE;
read = ACL_EXECUTE;
break;
case ACL_USAGE_CHR:
privs |= ACL_USAGE;
read = ACL_USAGE;
break;
case ACL_CREATE_CHR:
privs |= ACL_CREATE;
read = ACL_CREATE;
break;
case ACL_CREATE_TEMP_CHR:
privs |= ACL_CREATE_TEMP;
read = ACL_CREATE_TEMP;
break;
default:
elog(ERROR, "aclparse: mode flags must use \"%s\"",
ACL_ALL_RIGHTS_STR);
}
privs |= read;
}
switch (idtype)
{
case ACL_IDTYPE_UID:
aip->ai_id = get_usesysid(name);
aip->ai_grantee = get_usesysid(name);
break;
case ACL_IDTYPE_GID:
aip->ai_id = get_grosysid(name);
aip->ai_grantee = get_grosysid(name);
break;
case ACL_IDTYPE_WORLD:
aip->ai_id = ACL_ID_WORLD;
aip->ai_grantee = ACL_ID_WORLD;
break;
}
ACLITEM_SET_PRIVS_IDTYPE(*aip, privs, idtype);
/* 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')
elog(ERROR, "aclparse: a name must follow the \"/\" sign");
aip->ai_grantor = get_usesysid(name2);
}
else
{
aip->ai_grantor = BOOTSTRAP_USESYSID;
elog(WARNING, "defaulting grantor to %u", BOOTSTRAP_USESYSID);
}
ACLITEM_SET_PRIVS_IDTYPE(*aip, privs, goption, idtype);
#ifdef ACLDEBUG
elog(LOG, "aclparse: correctly read [%x %d %x], modechg=%x",
idtype, aip->ai_id, privs, *modechg);
elog(LOG, "aclparse: correctly read [%x %d %x]",
idtype, aip->ai_grantee, privs);
#endif
return s;
}
@ -271,12 +276,9 @@ aclitemin(PG_FUNCTION_ARGS)
{
const char *s = PG_GETARG_CSTRING(0);
AclItem *aip;
unsigned modechg;
aip = (AclItem *) palloc(sizeof(AclItem));
s = aclparse(s, aip, &modechg);
if (modechg != ACL_MODECHG_EQL)
elog(ERROR, "aclitemin: cannot accept anything but = ACLs");
s = aclparse(s, aip);
while (isspace((unsigned char) *s))
++s;
if (*s)
@ -302,14 +304,14 @@ aclitemout(PG_FUNCTION_ARGS)
unsigned i;
char *tmpname;
p = out = palloc(strlen("group = ") + N_ACL_RIGHTS + NAMEDATALEN + 1);
p = out = palloc(strlen("group = ") + 2 * N_ACL_RIGHTS + 2* NAMEDATALEN + 2);
*p = '\0';
switch (ACLITEM_GET_IDTYPE(*aip))
{
case ACL_IDTYPE_UID:
htup = SearchSysCache(SHADOWSYSID,
ObjectIdGetDatum(aip->ai_id),
ObjectIdGetDatum(aip->ai_grantee),
0, 0, 0);
if (HeapTupleIsValid(htup))
{
@ -324,14 +326,14 @@ aclitemout(PG_FUNCTION_ARGS)
char *tmp;
tmp = DatumGetCString(DirectFunctionCall1(int4out,
Int32GetDatum((int32) aip->ai_id)));
Int32GetDatum((int32) aip->ai_grantee)));
strcat(p, tmp);
pfree(tmp);
}
break;
case ACL_IDTYPE_GID:
strcat(p, "group ");
tmpname = get_groname(aip->ai_id);
tmpname = get_groname(aip->ai_grantee);
if (tmpname != NULL)
strncat(p, tmpname, NAMEDATALEN);
else
@ -340,7 +342,7 @@ aclitemout(PG_FUNCTION_ARGS)
char *tmp;
tmp = DatumGetCString(DirectFunctionCall1(int4out,
Int32GetDatum((int32) aip->ai_id)));
Int32GetDatum((int32) aip->ai_grantee)));
strcat(p, tmp);
pfree(tmp);
}
@ -354,10 +356,43 @@ aclitemout(PG_FUNCTION_ARGS)
}
while (*p)
++p;
*p++ = '=';
for (i = 0; i < N_ACL_RIGHTS; ++i)
if (aip->ai_privs & (1 << 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(SHADOWSYSID,
ObjectIdGetDatum(aip->ai_grantor),
0, 0, 0);
if (HeapTupleIsValid(htup))
{
strncat(p,
NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename),
NAMEDATALEN);
ReleaseSysCache(htup);
}
else
{
/* Generate numeric UID if we don't find an entry */
char *tmp;
tmp = DatumGetCString(DirectFunctionCall1(int4out,
Int32GetDatum((int32) aip->ai_grantor)));
strcat(p, tmp);
pfree(tmp);
}
while (*p)
++p;
*p = '\0';
PG_RETURN_CSTRING(out);
@ -365,29 +400,15 @@ aclitemout(PG_FUNCTION_ARGS)
/*
* aclitemeq
* aclitemgt
* AclItem equality and greater-than comparison routines.
* Two AclItems are considered equal iff they have the same
* identifier (and identifier type); the privileges are ignored.
* Note that these routines are really only useful for sorting
* AclItems into identifier order.
*
* RETURNS:
* a boolean value indicating = or >
* grantee and grantor; the privileges are ignored.
*/
static bool
aclitemeq(const AclItem *a1, const AclItem *a2)
{
return ACLITEM_GET_IDTYPE(*a1) == ACLITEM_GET_IDTYPE(*a2) &&
a1->ai_id == a2->ai_id;
}
static bool
aclitemgt(const AclItem *a1, const AclItem *a2)
{
return ((ACLITEM_GET_IDTYPE(*a1) > ACLITEM_GET_IDTYPE(*a2)) ||
(ACLITEM_GET_IDTYPE(*a1) == ACLITEM_GET_IDTYPE(*a2) &&
a1->ai_id > a2->ai_id));
a1->ai_grantee == a2->ai_grantee &&
a1->ai_grantor == a2->ai_grantor;
}
@ -436,15 +457,25 @@ acldefault(GrantObjectType objtype, AclId ownerid)
break;
}
acl = makeacl(ownerid ? 2 : 1);
acl = makeacl((world_default != ACL_NO_RIGHTS ? 1 : 0)
+ (ownerid ? 1 : 0));
aip = ACL_DAT(acl);
aip[0].ai_id = ACL_ID_WORLD;
ACLITEM_SET_PRIVS_IDTYPE(aip[0], world_default, ACL_IDTYPE_WORLD);
if (world_default != ACL_NO_RIGHTS)
{
aip[0].ai_grantee = ACL_ID_WORLD;
aip[0].ai_grantor = ownerid;
ACLITEM_SET_PRIVS_IDTYPE(aip[0], world_default, ACL_NO_RIGHTS, ACL_IDTYPE_WORLD);
}
if (ownerid)
{
aip[1].ai_id = ownerid;
ACLITEM_SET_PRIVS_IDTYPE(aip[1], owner_default, ACL_IDTYPE_UID);
int index = (world_default != ACL_NO_RIGHTS ? 1: 0);
aip[index].ai_grantee = ownerid;
aip[index].ai_grantor = ownerid;
/* owner gets default privileges with grant option */
ACLITEM_SET_PRIVS_IDTYPE(aip[index], owner_default, owner_default, ACL_IDTYPE_UID);
}
return acl;
@ -458,7 +489,7 @@ acldefault(GrantObjectType objtype, AclId ownerid)
* NB: caller is responsible for having detoasted the input ACL, if needed.
*/
Acl *
aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg, DropBehavior behavior)
{
Acl *new_acl;
AclItem *old_aip,
@ -480,49 +511,35 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
old_aip = ACL_DAT(old_acl);
/*
* Search the ACL for an existing entry for 'id'. 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 in
* sort-order.
* 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.
*/
/* find the first element not less than the element to be inserted */
for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
;
if (dst < num && aclitemeq(mod_aip, old_aip + dst))
for (dst = 0; dst < num; ++dst)
{
/* found a match, so modify existing item */
new_acl = makeacl(num);
new_aip = ACL_DAT(new_acl);
memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
if (aclitemeq(mod_aip, old_aip + dst))
{
/* found a match, so modify existing item */
new_acl = makeacl(num);
new_aip = ACL_DAT(new_acl);
memcpy(new_acl, old_acl, ACL_SIZE(old_acl));
break;
}
}
else
if (dst == num)
{
/* need to insert a new item */
/* need to append a new item */
new_acl = makeacl(num + 1);
new_aip = ACL_DAT(new_acl);
if (dst == 0)
{ /* start */
elog(ERROR, "aclinsert3: insertion before world ACL??");
}
else if (dst >= num)
{ /* end */
memcpy((char *) new_aip,
(char *) old_aip,
num * sizeof(AclItem));
}
else
{ /* middle */
memcpy((char *) new_aip,
(char *) old_aip,
dst * sizeof(AclItem));
memcpy((char *) (new_aip + dst + 1),
(char *) (old_aip + dst),
(num - dst) * sizeof(AclItem));
}
memcpy(new_aip, old_aip, num * sizeof(AclItem));
/* initialize the new entry with no permissions */
new_aip[dst].ai_id = mod_aip->ai_id;
ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst], ACL_NO_RIGHTS,
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_GET_IDTYPE(*mod_aip));
num++; /* set num to the size of new_acl */
}
@ -531,35 +548,89 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
switch (modechg)
{
case ACL_MODECHG_ADD:
new_aip[dst].ai_privs |= ACLITEM_GET_PRIVS(*mod_aip);
ACLITEM_SET_PRIVS(new_aip[dst], ACLITEM_GET_PRIVS(new_aip[dst]) | ACLITEM_GET_PRIVS(*mod_aip));
ACLITEM_SET_GOPTIONS(new_aip[dst], ACLITEM_GET_GOPTIONS(new_aip[dst]) | ACLITEM_GET_GOPTIONS(*mod_aip));
break;
case ACL_MODECHG_DEL:
new_aip[dst].ai_privs &= ~ACLITEM_GET_PRIVS(*mod_aip);
ACLITEM_SET_PRIVS(new_aip[dst], ACLITEM_GET_PRIVS(new_aip[dst]) & ~ACLITEM_GET_PRIVS(*mod_aip));
ACLITEM_SET_GOPTIONS(new_aip[dst], ACLITEM_GET_GOPTIONS(new_aip[dst]) & ~ACLITEM_GET_GOPTIONS(*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]));
break;
}
/*
* if the adjusted entry has no permissions, delete it from the list.
* For example, this helps in removing entries for users who no longer
* exist. EXCEPTION: never remove the world entry.
* If the adjusted entry has no permissions, delete it from the list.
*/
if (ACLITEM_GET_PRIVS(new_aip[dst]) == ACL_NO_RIGHTS && dst > 0)
if (ACLITEM_GET_PRIVS(new_aip[dst]) == ACL_NO_RIGHTS)
{
memmove((char *) (new_aip + dst),
(char *) (new_aip + dst + 1),
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)
*/
if (modechg != ACL_MODECHG_ADD
&& ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID
&& ACLITEM_GET_GOPTIONS(*mod_aip))
new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee, ACLITEM_GET_GOPTIONS(*mod_aip), behavior);
return new_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.
*/
static Acl *
recursive_revoke(Acl *acl,
AclId grantee,
AclMode revoke_privs,
DropBehavior behavior)
{
int i;
restart:
for (i = 0; i < ACL_NUM(acl); i++)
{
AclItem *aip = ACL_DAT(acl);
if (aip[i].ai_grantor == grantee
&& (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0)
{
AclItem mod_acl;
if (behavior == DROP_RESTRICT)
elog(ERROR, "dependent privileges exist (use CASCADE to revoke them too)");
mod_acl.ai_grantor = grantee;
mod_acl.ai_grantee = aip[i].ai_grantee;
ACLITEM_SET_PRIVS_IDTYPE(mod_acl,
revoke_privs,
revoke_privs,
ACLITEM_GET_IDTYPE(aip[i]));
acl = aclinsert3(acl, &mod_acl, ACL_MODECHG_DEL, behavior);
goto restart;
}
}
return acl;
}
/*
* aclinsert (exported function)
*/
@ -569,7 +640,7 @@ aclinsert(PG_FUNCTION_ARGS)
Acl *old_acl = PG_GETARG_ACL_P(0);
AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL, DROP_CASCADE));
}
Datum
@ -649,7 +720,7 @@ aclcontains(PG_FUNCTION_ARGS)
aidat = ACL_DAT(acl);
for (i = 0; i < num; ++i)
{
if (aip->ai_id == aidat[i].ai_id &&
if (aip->ai_grantee == aidat[i].ai_grantee &&
aip->ai_privs == aidat[i].ai_privs)
PG_RETURN_BOOL(true);
}
@ -842,24 +913,38 @@ convert_table_priv_string(text *priv_type_text)
*/
if (strcasecmp(priv_type, "SELECT") == 0)
return ACL_SELECT;
if (strcasecmp(priv_type, "SELECT WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_SELECT);
if (strcasecmp(priv_type, "INSERT") == 0)
return ACL_INSERT;
if (strcasecmp(priv_type, "INSERT WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_INSERT);
if (strcasecmp(priv_type, "UPDATE") == 0)
return ACL_UPDATE;
if (strcasecmp(priv_type, "UPDATE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_UPDATE);
if (strcasecmp(priv_type, "DELETE") == 0)
return ACL_DELETE;
if (strcasecmp(priv_type, "DELETE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_DELETE);
if (strcasecmp(priv_type, "RULE") == 0)
return ACL_RULE;
if (strcasecmp(priv_type, "RULE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_RULE);
if (strcasecmp(priv_type, "REFERENCES") == 0)
return ACL_REFERENCES;
if (strcasecmp(priv_type, "REFERENCES WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_REFERENCES);
if (strcasecmp(priv_type, "TRIGGER") == 0)
return ACL_TRIGGER;
if (strcasecmp(priv_type, "TRIGGER WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_TRIGGER);
elog(ERROR, "has_table_privilege: invalid privilege type %s",
priv_type);
@ -1057,12 +1142,18 @@ convert_database_priv_string(text *priv_type_text)
*/
if (strcasecmp(priv_type, "CREATE") == 0)
return ACL_CREATE;
if (strcasecmp(priv_type, "CREATE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_CREATE);
if (strcasecmp(priv_type, "TEMPORARY") == 0)
return ACL_CREATE_TEMP;
if (strcasecmp(priv_type, "TEMPORARY WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP);
if (strcasecmp(priv_type, "TEMP") == 0)
return ACL_CREATE_TEMP;
if (strcasecmp(priv_type, "TEMP WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP);
elog(ERROR, "has_database_privilege: invalid privilege type %s",
priv_type);
@ -1262,6 +1353,8 @@ convert_function_priv_string(text *priv_type_text)
*/
if (strcasecmp(priv_type, "EXECUTE") == 0)
return ACL_EXECUTE;
if (strcasecmp(priv_type, "EXECUTE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_EXECUTE);
elog(ERROR, "has_function_privilege: invalid privilege type %s",
priv_type);
@ -1461,6 +1554,8 @@ convert_language_priv_string(text *priv_type_text)
*/
if (strcasecmp(priv_type, "USAGE") == 0)
return ACL_USAGE;
if (strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_USAGE);
elog(ERROR, "has_language_privilege: invalid privilege type %s",
priv_type);
@ -1660,9 +1755,13 @@ convert_schema_priv_string(text *priv_type_text)
*/
if (strcasecmp(priv_type, "CREATE") == 0)
return ACL_CREATE;
if (strcasecmp(priv_type, "CREATE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_CREATE);
if (strcasecmp(priv_type, "USAGE") == 0)
return ACL_USAGE;
if (strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_USAGE);
elog(ERROR, "has_schema_privilege: invalid privilege type %s",
priv_type);