diff options
Diffstat (limited to 'src/backend/commands/seclabel.c')
-rw-r--r-- | src/backend/commands/seclabel.c | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c new file mode 100644 index 00000000000..417ad88d557 --- /dev/null +++ b/src/backend/commands/seclabel.c @@ -0,0 +1,387 @@ +/* ------------------------------------------------------------------------- + * + * seclabel.c + * routines to support security label feature. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_seclabel.h" +#include "commands/seclabel.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/tqual.h" + +/* + * For most object types, the permissions-checking logic is simple enough + * that it makes sense to just include it in CommentObject(). However, + * attributes require a bit more checking. + */ +static void CheckAttributeSecLabel(Relation relation); + +typedef struct +{ + const char *provider_name; + check_object_relabel_type hook; +} LabelProvider; + +static List *label_provider_list = NIL; + +/* + * ExecSecLabelStmt -- + * + * Apply a security label to a database object. + */ +void +ExecSecLabelStmt(SecLabelStmt *stmt) +{ + LabelProvider *provider = NULL; + ObjectAddress address; + Relation relation; + ListCell *lc; + + /* + * Find the named label provider, or if none specified, check whether + * there's exactly one, and if so use it. + */ + if (stmt->provider == NULL) + { + if (label_provider_list == NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("security label providers have been loaded"))); + if (lnext(list_head(label_provider_list)) != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("must specify provider when multiple security label providers have been loaded"))); + provider = (LabelProvider *) linitial(label_provider_list); + } + else + { + foreach (lc, label_provider_list) + { + LabelProvider *lp = lfirst(lc); + + if (strcmp(stmt->provider, lp->provider_name) == 0) + { + provider = lp; + break; + } + } + if (provider == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("security label provider \"%s\" is not loaded", + stmt->provider))); + } + + /* + * Translate the parser representation which identifies this object + * into an ObjectAddress. get_object_address() will throw an error if + * the object does not exist, and will also acquire a lock on the + * target to guard against concurrent modifications. + */ + address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs, + &relation, ShareUpdateExclusiveLock); + + /* Privilege and integrity checks. */ + switch (stmt->objtype) + { + case OBJECT_SEQUENCE: + case OBJECT_TABLE: + case OBJECT_VIEW: + if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(relation)); + break; + case OBJECT_COLUMN: + CheckAttributeSecLabel(relation); + break; + case OBJECT_TYPE: + if (!pg_type_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, + format_type_be(address.objectId)); + break; + case OBJECT_AGGREGATE: + case OBJECT_FUNCTION: + if (!pg_proc_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, + NameListToString(stmt->objname)); + break; + case OBJECT_SCHEMA: + if (!pg_namespace_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, + strVal(linitial(stmt->objname))); + break; + case OBJECT_LANGUAGE: + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to comment on procedural language"))); + break; + case OBJECT_LARGEOBJECT: + if (!pg_largeobject_ownercheck(address.objectId, GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be owner of large object %u", + address.objectId))); + break; + default: + elog(ERROR, "unrecognized object type: %d", + (int) stmt->objtype); + } + + /* Provider gets control here, may throw ERROR to veto new label. */ + (*provider->hook)(&address, stmt->label); + + /* Apply new label. */ + SetSecurityLabel(&address, provider->provider_name, stmt->label); + + /* + * If get_object_address() opened the relation for us, we close it to keep + * the reference count correct - but we retain any locks acquired by + * get_object_address() until commit time, to guard against concurrent + * activity. + */ + if (relation != NULL) + relation_close(relation, NoLock); +} + +/* + * GetSecurityLabel returns the security label for a database object for a + * given provider, or NULL if there is no such label. + */ +char * +GetSecurityLabel(const ObjectAddress *object, const char *provider) +{ + Relation pg_seclabel; + ScanKeyData keys[4]; + SysScanDesc scan; + HeapTuple tuple; + Datum datum; + bool isnull; + char *seclabel = NULL; + + Assert(!IsSharedRelation(object->classId)); + + ScanKeyInit(&keys[0], + Anum_pg_seclabel_objoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + ScanKeyInit(&keys[1], + Anum_pg_seclabel_classoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->classId)); + ScanKeyInit(&keys[2], + Anum_pg_seclabel_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + ScanKeyInit(&keys[3], + Anum_pg_seclabel_provider, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(provider)); + + pg_seclabel = heap_open(SecLabelRelationId, AccessShareLock); + + scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, + SnapshotNow, 4, keys); + + tuple = systable_getnext(scan); + if (HeapTupleIsValid(tuple)) + { + datum = heap_getattr(tuple, Anum_pg_seclabel_label, + RelationGetDescr(pg_seclabel), &isnull); + if (!isnull) + seclabel = TextDatumGetCString(datum); + } + systable_endscan(scan); + + heap_close(pg_seclabel, AccessShareLock); + + return seclabel; +} + +/* + * SetSecurityLabel attempts to set the security label for the specified + * provider on the specified object to the given value. NULL means that any + * any existing label should be deleted. + */ +void +SetSecurityLabel(const ObjectAddress *object, + const char *provider, const char *label) +{ + Relation pg_seclabel; + ScanKeyData keys[4]; + SysScanDesc scan; + HeapTuple oldtup; + HeapTuple newtup = NULL; + Datum values[Natts_pg_seclabel]; + bool nulls[Natts_pg_seclabel]; + bool replaces[Natts_pg_seclabel]; + + /* Security labels on shared objects are not supported. */ + Assert(!IsSharedRelation(object->classId)); + + /* Prepare to form or update a tuple, if necessary. */ + memset(nulls, false, sizeof(nulls)); + memset(replaces, false, sizeof(replaces)); + values[Anum_pg_seclabel_objoid - 1] = ObjectIdGetDatum(object->objectId); + values[Anum_pg_seclabel_classoid - 1] = ObjectIdGetDatum(object->classId); + values[Anum_pg_seclabel_objsubid - 1] = Int32GetDatum(object->objectSubId); + values[Anum_pg_seclabel_provider - 1] = CStringGetTextDatum(provider); + if (label != NULL) + values[Anum_pg_seclabel_label - 1] = CStringGetTextDatum(label); + + /* Use the index to search for a matching old tuple */ + ScanKeyInit(&keys[0], + Anum_pg_seclabel_objoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + ScanKeyInit(&keys[1], + Anum_pg_seclabel_classoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->classId)); + ScanKeyInit(&keys[2], + Anum_pg_seclabel_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + ScanKeyInit(&keys[3], + Anum_pg_seclabel_provider, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(provider)); + + pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock); + + scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, + SnapshotNow, 4, keys); + + oldtup = systable_getnext(scan); + if (HeapTupleIsValid(oldtup)) + { + if (label == NULL) + simple_heap_delete(pg_seclabel, &oldtup->t_self); + else + { + replaces[Anum_pg_seclabel_label - 1] = true; + newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_seclabel), + values, nulls, replaces); + simple_heap_update(pg_seclabel, &oldtup->t_self, newtup); + } + } + systable_endscan(scan); + + /* If we didn't find an old tuple, insert a new one */ + if (newtup == NULL && label != NULL) + { + newtup = heap_form_tuple(RelationGetDescr(pg_seclabel), + values, nulls); + simple_heap_insert(pg_seclabel, newtup); + } + + /* Update indexes, if necessary */ + if (newtup != NULL) + { + CatalogUpdateIndexes(pg_seclabel, newtup); + heap_freetuple(newtup); + } + + heap_close(pg_seclabel, RowExclusiveLock); +} + +/* + * DeleteSecurityLabel removes all security labels for an object (and any + * sub-objects, if applicable). + */ +void +DeleteSecurityLabel(const ObjectAddress *object) +{ + Relation pg_seclabel; + ScanKeyData skey[3]; + SysScanDesc scan; + HeapTuple oldtup; + int nkeys; + + /* Security labels on shared objects are not supported. */ + if (IsSharedRelation(object->classId)) + return; + + ScanKeyInit(&skey[0], + Anum_pg_seclabel_objoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + ScanKeyInit(&skey[1], + Anum_pg_seclabel_classoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->classId)); + if (object->objectSubId != 0) + { + ScanKeyInit(&skey[2], + Anum_pg_seclabel_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + nkeys = 3; + } + else + nkeys = 2; + + pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock); + + scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, + SnapshotNow, nkeys, skey); + while (HeapTupleIsValid(oldtup = systable_getnext(scan))) + simple_heap_delete(pg_seclabel, &oldtup->t_self); + systable_endscan(scan); + + heap_close(pg_seclabel, RowExclusiveLock); +} + +/* + * Check whether the user is allowed to comment on an attribute of the + * specified relation. + */ +static void +CheckAttributeSecLabel(Relation relation) +{ + if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(relation)); + + /* + * Allow security labels only on columns of tables, views, and composite + * types (which are the only relkinds for which pg_dump will dump labels). + */ + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_VIEW && + relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table, view, or composite type", + RelationGetRelationName(relation)))); +} + +void +register_label_provider(const char *provider_name, check_object_relabel_type hook) +{ + LabelProvider *provider; + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + provider = palloc(sizeof(LabelProvider)); + provider->provider_name = pstrdup(provider_name); + provider->hook = hook; + label_provider_list = lappend(label_provider_list, provider); + MemoryContextSwitchTo(oldcxt); +} |