diff options
Diffstat (limited to 'src/backend/catalog/aclchk.c')
-rw-r--r-- | src/backend/catalog/aclchk.c | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c new file mode 100644 index 00000000000..752399e7615 --- /dev/null +++ b/src/backend/catalog/aclchk.c @@ -0,0 +1,666 @@ +/*------------------------------------------------------------------------- + * + * aclchk.c-- + * Routines to check access control permissions. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.1 1998/01/05 18:42:40 momjian Exp $ + * + * NOTES + * See acl.h. + * + *------------------------------------------------------------------------- + */ +#include <string.h> +#include "postgres.h" + +#include "utils/acl.h" /* where declarations for this file go */ +#include "access/heapam.h" +#include "access/htup.h" +#include "access/tupmacs.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "catalog/indexing.h" +#include "catalog/catalog.h" +#include "catalog/catname.h" +#include "catalog/pg_group.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "catalog/pg_user.h" +#include "parser/parse_agg.h" +#include "parser/parse_func.h" +#include "utils/syscache.h" +#include "utils/tqual.h" +#include "fmgr.h" + +static int32 aclcheck(Acl *acl, AclId id, AclIdType idtype, AclMode mode); + +/* + * Enable use of user relations in place of real system catalogs. + */ +/*#define ACLDEBUG*/ + +#ifdef ACLDEBUG +/* + * Fool the code below into thinking that "pgacls" is pg_class. + * relname and relowner are in the same place, happily. + */ +#undef Anum_pg_class_relacl +#define Anum_pg_class_relacl 3 +#undef Natts_pg_class +#define Natts_pg_class 3 +#undef Name_pg_class +#define Name_pg_class "pgacls" +#undef Name_pg_group +#define Name_pg_group "pggroup" +#endif + +/* warning messages, now more explicit. */ +/* should correspond to the order of the ACLCHK_* result codes above. */ +char *aclcheck_error_strings[] = { + "No error.", + "Permission denied.", + "Table does not exist.", + "Must be table owner." +}; + +#ifdef ACLDEBUG_TRACE +static +dumpacl(Acl *acl) +{ + register unsigned i; + AclItem *aip; + + elog(DEBUG, "acl size = %d, # acls = %d", + ACL_SIZE(acl), ACL_NUM(acl)); + aip = (AclItem *) ACL_DAT(acl); + for (i = 0; i < ACL_NUM(acl); ++i) + elog(DEBUG, " acl[%d]: %s", i, aclitemout(aip + i)); +} + +#endif + +/* + * + */ +void +ChangeAcl(char *relname, + AclItem *mod_aip, + unsigned modechg) +{ + register unsigned i; + Acl *old_acl = (Acl *) NULL, + *new_acl; + Relation relation; + static ScanKeyData relkey[1] = { + {0, Anum_pg_class_relname, NameEqualRegProcedure} + }; + HeapScanDesc hsdp; + HeapTuple htp; + Buffer buffer; + Datum values[Natts_pg_class]; + char nulls[Natts_pg_class]; + char replaces[Natts_pg_class]; + ItemPointerData tmp_ipd; + Relation idescs[Num_pg_class_indices]; + int free_old_acl = 0; + + /* + * Find the pg_class tuple matching 'relname' and extract the ACL. If + * there's no ACL, create a default using the pg_class.relowner field. + * + * We can't use the syscache here, since we need to do a heap_replace on + * the tuple we find. Feh. + */ + relation = heap_openr(RelationRelationName); + if (!RelationIsValid(relation)) + elog(ERROR, "ChangeAcl: could not open '%s'??", + RelationRelationName); + fmgr_info(NameEqualRegProcedure, &relkey[0].sk_func, &relkey[0].sk_nargs); + relkey[0].sk_argument = NameGetDatum(relname); + hsdp = heap_beginscan(relation, + 0, + false, + (unsigned) 1, + relkey); + htp = heap_getnext(hsdp, 0, &buffer); + if (!HeapTupleIsValid(htp)) + { + heap_endscan(hsdp); + heap_close(relation); + elog(ERROR, "ChangeAcl: class \"%s\" not found", + relname); + return; + } + if (!heap_attisnull(htp, Anum_pg_class_relacl)) + old_acl = (Acl *) heap_getattr(htp, buffer, + Anum_pg_class_relacl, + RelationGetTupleDescriptor(relation), + (bool *) NULL); + if (!old_acl || ACL_NUM(old_acl) < 1) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "ChangeAcl: using default ACL"); +#endif +/* old_acl = acldefault(((Form_pg_class) GETSTRUCT(htp))->relowner); */ + old_acl = acldefault(); + free_old_acl = 1; + } + +#ifdef ACLDEBUG_TRACE + dumpacl(old_acl); +#endif + new_acl = aclinsert3(old_acl, mod_aip, modechg); +#ifdef ACLDEBUG_TRACE + dumpacl(new_acl); +#endif + + for (i = 0; i < Natts_pg_class; ++i) + { + replaces[i] = ' '; + nulls[i] = ' '; /* ignored if replaces[i] == ' ' anyway */ + values[i] = (Datum) NULL; /* ignored if replaces[i] == ' ' + * anyway */ + } + replaces[Anum_pg_class_relacl - 1] = 'r'; + values[Anum_pg_class_relacl - 1] = (Datum) new_acl; + htp = heap_modifytuple(htp, buffer, relation, values, nulls, replaces); + /* XXX is this necessary? */ + ItemPointerCopy(&htp->t_ctid, &tmp_ipd); + /* XXX handle index on pg_class? */ + setheapoverride(true); + heap_replace(relation, &tmp_ipd, htp); + setheapoverride(false); + heap_endscan(hsdp); + + /* keep the catalog indices up to date */ + CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, + idescs); + CatalogIndexInsert(idescs, Num_pg_class_indices, relation, htp); + CatalogCloseIndices(Num_pg_class_indices, idescs); + + heap_close(relation); + if (free_old_acl) + pfree(old_acl); + pfree(new_acl); +} + +AclId +get_grosysid(char *groname) +{ + HeapTuple htp; + AclId id = 0; + + htp = SearchSysCacheTuple(GRONAME, PointerGetDatum(groname), + 0, 0, 0); + if (HeapTupleIsValid(htp)) + { + id = ((Form_pg_group) GETSTRUCT(htp))->grosysid; + } + else + { + elog(ERROR, "non-existent group \"%s\"", groname); + } + return (id); +} + +char * +get_groname(AclId grosysid) +{ + HeapTuple htp; + char *name = NULL; + + htp = SearchSysCacheTuple(GROSYSID, PointerGetDatum(grosysid), + 0, 0, 0); + if (HeapTupleIsValid(htp)) + { + name = (((Form_pg_group) GETSTRUCT(htp))->groname).data; + } + else + { + elog(NOTICE, "get_groname: group %d not found", grosysid); + } + return (name); +} + +static int32 +in_group(AclId uid, AclId gid) +{ + Relation relation; + HeapTuple htp; + Acl *tmp; + unsigned i, + num; + AclId *aidp; + int32 found = 0; + + relation = heap_openr(GroupRelationName); + if (!RelationIsValid(relation)) + { + elog(NOTICE, "in_group: could not open \"%s\"??", + GroupRelationName); + return (0); + } + htp = SearchSysCacheTuple(GROSYSID, ObjectIdGetDatum(gid), + 0, 0, 0); + if (HeapTupleIsValid(htp) && + !heap_attisnull(htp, Anum_pg_group_grolist)) + { + tmp = (IdList *) heap_getattr(htp, InvalidBuffer, + Anum_pg_group_grolist, + RelationGetTupleDescriptor(relation), + (bool *) NULL); + /* XXX make me a function */ + num = IDLIST_NUM(tmp); + aidp = IDLIST_DAT(tmp); + for (i = 0; i < num; ++i) + if (aidp[i] == uid) + { + found = 1; + break; + } + } + else + { + elog(NOTICE, "in_group: group %d not found", gid); + } + heap_close(relation); + return (found); +} + +/* + * aclcheck + * Returns 1 if the 'id' of type 'idtype' has ACL entries in 'acl' to satisfy + * any one of the requirements of 'mode'. Returns 0 otherwise. + */ +static int32 +aclcheck(Acl *acl, AclId id, AclIdType idtype, AclMode mode) +{ + register unsigned i; + register AclItem *aip, + *aidat; + unsigned num, + found_group; + + /* if no acl is found, use world default */ + if (!acl) + { + acl = acldefault(); + } + + 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. + */ + if (num < 1) + { +#if ACLDEBUG_TRACE || 1 + elog(DEBUG, "aclcheck: zero-length ACL, returning 1"); +#endif + return ACLCHECK_OK; + } + + switch (idtype) + { + case ACL_IDTYPE_UID: + for (i = 1, aip = aidat + 1; /* skip world entry */ + i < num && aip->ai_idtype == ACL_IDTYPE_UID; + ++i, ++aip) + { + if (aip->ai_id == id) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "aclcheck: found %d/%d", + aip->ai_id, aip->ai_mode); +#endif + return ((aip->ai_mode & mode) ? ACLCHECK_OK : ACLCHECK_NO_PRIV); + } + } + for (found_group = 0; + i < num && aip->ai_idtype == ACL_IDTYPE_GID; + ++i, ++aip) + { + if (in_group(id, aip->ai_id)) + { + if (aip->ai_mode & mode) + { + found_group = 1; + break; + } + } + } + if (found_group) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "aclcheck: all groups ok"); +#endif + return ACLCHECK_OK; + } + break; + case ACL_IDTYPE_GID: + for (i = 1, aip = aidat + 1; /* skip world entry and + * UIDs */ + i < num && aip->ai_idtype == ACL_IDTYPE_UID; + ++i, ++aip) + ; + for (; + i < num && aip->ai_idtype == ACL_IDTYPE_GID; + ++i, ++aip) + { + if (aip->ai_id == id) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "aclcheck: found %d/%d", + aip->ai_id, aip->ai_mode); +#endif + return ((aip->ai_mode & mode) ? ACLCHECK_OK : ACLCHECK_NO_PRIV); + } + } + break; + case ACL_IDTYPE_WORLD: + break; + default: + elog(ERROR, "aclcheck: bogus ACL id type: %d", idtype); + break; + } + +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "aclcheck: using world=%d", aidat->ai_mode); +#endif + return ((aidat->ai_mode & mode) ? ACLCHECK_OK : ACLCHECK_NO_PRIV); +} + +int32 +pg_aclcheck(char *relname, char *usename, AclMode mode) +{ + HeapTuple htp; + AclId id; + Acl *acl = (Acl *) NULL, + *tmp; + int32 result; + Relation relation; + + htp = SearchSysCacheTuple(USENAME, PointerGetDatum(usename), + 0, 0, 0); + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_aclcheck: user \"%s\" not found", + usename); + id = (AclId) ((Form_pg_user) GETSTRUCT(htp))->usesysid; + + /* + * for the 'pg_database' relation, check the usecreatedb field before + * checking normal permissions + */ + if (strcmp(DatabaseRelationName, relname) == 0 && + (((Form_pg_user) GETSTRUCT(htp))->usecreatedb)) + { + + /* + * note that even though the user can now append to the + * pg_database table, there is still additional permissions + * checking in dbcommands.c + */ + if (mode & ACL_AP) + return ACLCHECK_OK; + } + + /* + * Deny anyone permission to update a system catalog unless + * pg_user.usecatupd is set. (This is to let superusers protect + * themselves from themselves.) + */ + if (((mode & ACL_WR) || (mode & ACL_AP)) && + IsSystemRelationName(relname) && + !((Form_pg_user) GETSTRUCT(htp))->usecatupd) + { + elog(DEBUG, "pg_aclcheck: catalog update to \"%s\": permission denied", + relname); + return ACLCHECK_NO_PRIV; + } + + /* + * Otherwise, superusers bypass all permission-checking. + */ + if (((Form_pg_user) GETSTRUCT(htp))->usesuper) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "pg_aclcheck: \"%s\" is superuser", + usename); +#endif + return ACLCHECK_OK; + } + +#ifndef ACLDEBUG + htp = SearchSysCacheTuple(RELNAME, PointerGetDatum(relname), + 0, 0, 0); + if (!HeapTupleIsValid(htp)) + { + elog(ERROR, "pg_aclcheck: class \"%s\" not found", + relname); + /* an elog(ERROR) kills us, so no need to return anything. */ + } + if (!heap_attisnull(htp, Anum_pg_class_relacl)) + { + relation = heap_openr(RelationRelationName); + tmp = (Acl *) heap_getattr(htp, InvalidBuffer, + Anum_pg_class_relacl, + RelationGetTupleDescriptor(relation), + (bool *) NULL); + acl = makeacl(ACL_NUM(tmp)); + memmove((char *) acl, (char *) tmp, ACL_SIZE(tmp)); + heap_close(relation); + } + else + { + + /* + * if the acl is null, by default the owner can do whatever he + * wants to with it + */ + Oid ownerId; + + relation = heap_openr(RelationRelationName); + ownerId = (Oid) heap_getattr(htp, InvalidBuffer, + Anum_pg_class_relowner, + RelationGetTupleDescriptor(relation), + (bool *) NULL); + acl = aclownerdefault(ownerId); + } +#else + { /* This is why the syscache is great... */ + static ScanKeyData relkey[1] = { + {0, Anum_pg_class_relname, NameEqualRegProcedure} + }; + HeapScanDesc hsdp; + + relation = heap_openr(RelationRelationName); + if (!RelationIsValid(relation)) + { + elog(NOTICE, "pg_checkacl: could not open \"%-.*s\"??", + RelationRelationName); + return ACLCHECK_NO_CLASS; + } + fmgr_info(NameEqualRegProcedure, + &relkey[0].sk_func, + &relkey[0].sk_nargs); + relkey[0].sk_argument = NameGetDatum(relname); + hsdp = heap_beginscan(relation, 0, false, 1, relkey); + htp = heap_getnext(hsdp, 0, (Buffer *) 0); + if (HeapTupleIsValid(htp) && + !heap_attisnull(htp, Anum_pg_class_relacl)) + { + tmp = (Acl *) heap_getattr(htp, InvalidBuffer, + Anum_pg_class_relacl, + RelationGetTupleDescriptor(relation), + (bool *) NULL); + acl = makeacl(ACL_NUM(tmp)); + memmove((char *) acl, (char *) tmp, ACL_SIZE(tmp)); + } + heap_endscan(hsdp); + heap_close(relation); + } +#endif + result = aclcheck(acl, id, (AclIdType) ACL_IDTYPE_UID, mode); + if (acl) + pfree(acl); + return (result); +} + +int32 +pg_ownercheck(char *usename, + char *value, + int cacheid) +{ + HeapTuple htp; + AclId user_id, + owner_id = 0; + + htp = SearchSysCacheTuple(USENAME, PointerGetDatum(usename), + 0, 0, 0); + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_ownercheck: user \"%s\" not found", + usename); + user_id = (AclId) ((Form_pg_user) GETSTRUCT(htp))->usesysid; + + /* + * Superusers bypass all permission-checking. + */ + if (((Form_pg_user) GETSTRUCT(htp))->usesuper) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "pg_ownercheck: user \"%s\" is superuser", + usename); +#endif + return (1); + } + + htp = SearchSysCacheTuple(cacheid, PointerGetDatum(value), + 0, 0, 0); + switch (cacheid) + { + case OPROID: + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_ownercheck: operator %ld not found", + PointerGetDatum(value)); + owner_id = ((OperatorTupleForm) GETSTRUCT(htp))->oprowner; + break; + case PRONAME: + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_ownercheck: function \"%s\" not found", + value); + owner_id = ((Form_pg_proc) GETSTRUCT(htp))->proowner; + break; + case RELNAME: + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_ownercheck: class \"%s\" not found", + value); + owner_id = ((Form_pg_class) GETSTRUCT(htp))->relowner; + break; + case TYPNAME: + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_ownercheck: type \"%s\" not found", + value); + owner_id = ((TypeTupleForm) GETSTRUCT(htp))->typowner; + break; + default: + elog(ERROR, "pg_ownercheck: invalid cache id: %d", + cacheid); + break; + } + + return (user_id == owner_id); +} + +int32 +pg_func_ownercheck(char *usename, + char *funcname, + int nargs, + Oid *arglist) +{ + HeapTuple htp; + AclId user_id, + owner_id; + + htp = SearchSysCacheTuple(USENAME, PointerGetDatum(usename), + 0, 0, 0); + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_func_ownercheck: user \"%s\" not found", + usename); + user_id = (AclId) ((Form_pg_user) GETSTRUCT(htp))->usesysid; + + /* + * Superusers bypass all permission-checking. + */ + if (((Form_pg_user) GETSTRUCT(htp))->usesuper) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "pg_ownercheck: user \"%s\" is superuser", + usename); +#endif + return (1); + } + + htp = SearchSysCacheTuple(PRONAME, + PointerGetDatum(funcname), + PointerGetDatum(nargs), + PointerGetDatum(arglist), + 0); + if (!HeapTupleIsValid(htp)) + func_error("pg_func_ownercheck", funcname, nargs, arglist); + + owner_id = ((Form_pg_proc) GETSTRUCT(htp))->proowner; + + return (user_id == owner_id); +} + +int32 +pg_aggr_ownercheck(char *usename, + char *aggname, + Oid basetypeID) +{ + HeapTuple htp; + AclId user_id, + owner_id; + + htp = SearchSysCacheTuple(USENAME, PointerGetDatum(usename), + 0, 0, 0); + if (!HeapTupleIsValid(htp)) + elog(ERROR, "pg_aggr_ownercheck: user \"%s\" not found", + usename); + user_id = (AclId) ((Form_pg_user) GETSTRUCT(htp))->usesysid; + + /* + * Superusers bypass all permission-checking. + */ + if (((Form_pg_user) GETSTRUCT(htp))->usesuper) + { +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "pg_aggr_ownercheck: user \"%s\" is superuser", + usename); +#endif + return (1); + } + + htp = SearchSysCacheTuple(AGGNAME, + PointerGetDatum(aggname), + PointerGetDatum(basetypeID), + 0, + 0); + + if (!HeapTupleIsValid(htp)) + agg_error("pg_aggr_ownercheck", aggname, basetypeID); + + owner_id = ((Form_pg_aggregate) GETSTRUCT(htp))->aggowner; + + return (user_id == owner_id); +} |