/** * @file veil2.c * \code * Author: Marc Munro * Copyright (c) 2020 Marc Munro * License: GPL V3 * * \endcode * @brief * Provides callable veil2 functions. These are written in C for * performance and to ensure that they cannot be easily subverted. * */ #include "postgres.h" #include "funcapi.h" #include "catalog/pg_type.h" #include "access/xact.h" #include "executor/spi.h" #include "utils/builtins.h" #include "veil2.h" PG_MODULE_MAGIC; /* These definitions are here rather than immediately preceding the * function declarations themselves as this code seems to confuse * Doxygen's call graph stuff. */ PG_FUNCTION_INFO_V1(veil2_session_ready); PG_FUNCTION_INFO_V1(veil2_reset_session); PG_FUNCTION_INFO_V1(veil2_reset_session_privs); PG_FUNCTION_INFO_V1(veil2_session_context); PG_FUNCTION_INFO_V1(veil2_session_privileges); PG_FUNCTION_INFO_V1(veil2_add_session_privileges); PG_FUNCTION_INFO_V1(veil2_update_session_privileges); PG_FUNCTION_INFO_V1(veil2_true); PG_FUNCTION_INFO_V1(veil2_i_have_global_priv); PG_FUNCTION_INFO_V1(veil2_i_have_personal_priv); PG_FUNCTION_INFO_V1(veil2_i_have_priv_in_scope); PG_FUNCTION_INFO_V1(veil2_i_have_priv_in_scope_or_global); PG_FUNCTION_INFO_V1(veil2_i_have_priv_in_superior_scope); PG_FUNCTION_INFO_V1(veil2_i_have_priv_in_scope_or_superior); PG_FUNCTION_INFO_V1(veil2_i_have_priv_in_scope_or_superior_or_global); PG_FUNCTION_INFO_V1(veil2_result_counts); PG_FUNCTION_INFO_V1(veil2_docpath); PG_FUNCTION_INFO_V1(veil2_datapath); PG_FUNCTION_INFO_V1(veil2_version); /** * Used to record whether the current session's temporary tables have * been properly initialised using veil2_reset_session(). If not the * privilege testing functions veil2_i_have_global_priv(), * veil2_i_have_personal_priv(), veil2_i_have_priv_in_scope() and * veil2_i_have_priv_in_superior_scope() will always return false. * If you need to implement your own pl/pgsql base privilege testing * function, it should call veil2_session_reeady() to ensure that * privileges have been correctly set up. * * The primary reason for this variable to exist is to ensure that a * user cannot trick the privileges functions by creating their own * session_privileges table. */ static bool session_ready = false; /** * Used to record counts of false and true results from the * i_have_priv_xxx() functions. */ static int result_counts[] = {0, 0}; /** * Used to record an in-memory set of privileges associated with a * specfic scope (security context). */ typedef struct { int scope_type; int scope; Bitmap *roles; Bitmap *privileges; } ContextRolePrivs; /** * Used to record the set of ContextPrivs for the current user's session. */ typedef struct { /** How many ContextPrivs we can currently store. If we need * more, we have to rebuild this structure. */ int array_len; /** How many ContextPrivs we have for the current session. */ int active_contexts; ContextRolePrivs context_roleprivs[0]; } SessionRolePrivs; /** * Used to record our current session context. This replaces a * temporary table in an attempt to improve both security and * performance. */ typedef struct { bool loaded; int accessor_id; int64 session_id; int login_context_type_id; int login_context_id; int session_context_type_id; int session_context_id; int mapping_context_type_id; int mapping_context_id; /** parent_session_id is nullable. To indicate null we set it to * the same value as session_id. */ int64 parent_session_id; } SessionContext; /** * The SessionPrivs object for this session. */ static SessionRolePrivs *session_roleprivs = NULL; /** * Whether we have loaded our session's ContextPrivs into session memory. */ static bool session_roleprivs_loaded = false; static SessionContext session_context = {false, 0, 0, 0, 0, 0, 0, 0, 0}; /** * Locate a particular ContextPriv entry in ::session_roleprivs. * * @param p_idx Pointer to a cached index value for the entry in the * ::session_roleprivs->active_contexts that the search should start from. * This allows the caller to cache the last returned index in the hope * that they will be looking for the same entry next time. If no * cached value exists, the caller should provide -1. The index of * the found ContextPrivs entry will be returned through this, or -1 * if no context can be found. * @param scope_type The scope_type_id of the ContextPrivs entry we * are looking for. * @param scope The scope_id of the ContextPrivs entry we are looking * for. */ static void findContext(int *p_idx, int scope_type, int scope) { int this = *p_idx; int cmp; int lower = 0; int upper; ContextRolePrivs *this_cp; if (!session_roleprivs) { *p_idx = -1; return; } upper = session_roleprivs->active_contexts - 1; if (upper == 0) { *p_idx = -1; return; } else if ((this < 0) || (this >= upper)) { /* Create a new start, in the middle of the contexts. */ this = upper >> 1; } /* Bsearch until we find a match or realise there is none. */ while (true) { this_cp = &(session_roleprivs->context_roleprivs[this]); cmp = this_cp->scope_type - scope_type; if (!cmp) { cmp = this_cp->scope - scope; } if (!cmp) { *p_idx = this; return; } if (cmp > 0) { /* We are looking for a lower value. */ upper = this - 1; } else { lower = this + 1; } if (upper < lower) { *p_idx = -1; return; } this = (upper + lower) >> 1; } } /** * Wrapper for findContext() that finds the context and checks for a * privilege in a single operation. * * @param p_idx Pointer to a cached index value for the entry in the * ::session_roleprivs->active_contexts that the search should start from. * This allows the caller to cache the last returned index in the hope * that they will be looking for the same entry next time. If no * cached value exists, the caller should provide -1. The index of * the found ContextPrivs entry will be returned through this, or -1 * if no context can be found. * @param scope_type The scope_type_id of the ContextPrivs entry we * are looking for. * @param scope The scope_id of the ContextPrivs entry we are looking * for. * @param priv The privilege to test for. * * @return false if no context can be found, otherwise true if the * user has priv in the supplied scope. */ static bool checkContext(int *p_idx, int scope_type, int scope, int priv) { findContext(p_idx, scope_type, scope); if (*p_idx == -1) { return false; } return bitmapTestbit( session_roleprivs->context_roleprivs[*p_idx].privileges, priv); } /** * Free a ContextRolePrivs entry. This just means freeing the privileges * Bitmap and zeroing the pointer for it. * * @param cp The ContextRolePrivs entry to be cleared out. */ static void freeContextRolePrivs(ContextRolePrivs *cp) { pfree((void *) cp->roles); pfree((void *) cp->privileges); cp->roles = NULL; cp->privileges = NULL; } /** * Clear all ContextRolePrivs entries in session_roleprivs. */ static void clear_session_roleprivs() { int i; MemoryContext old_context; old_context = MemoryContextSwitchTo(TopMemoryContext); if (session_roleprivs) { for (i = session_roleprivs->active_contexts - 1; i >= 0; i--) { freeContextRolePrivs(&session_roleprivs->context_roleprivs[i]); } session_roleprivs->active_contexts = 0; session_roleprivs_loaded = false; } MemoryContextSwitchTo(old_context); } /** * How many ContextPrivs entries a SessionPrivs structure will be * created with or extended by. */ #define CONTEXT_ROLEPRIVS_INCREMENT 16 /** Provide the size that we want our SessionRolePrivs structure to be. * * @param elems the number of ContextRolePrivs entries already in * place. This will be increased by CONTEXT_ROLEPRIVS_INCREMENT. */ #define CONTEXT_ROLEPRIVS_SIZE(elems) ( \ sizeof(SessionRolePrivs) + \ (sizeof(ContextRolePrivs) * \ (elems + CONTEXT_ROLEPRIVS_INCREMENT))) /* * Create or extend our SessionRolePrivs structure. * * @param session_roleprivs, the current version of the struct, or * NULL, if it has not yet been created. * @result The newly allocated or extended SessionRolePrivs struct. */ static SessionRolePrivs * extendSessionRolePrivs(SessionRolePrivs *session_roleprivs) { size_t size; int i; if (session_roleprivs) { size = CONTEXT_ROLEPRIVS_SIZE(session_roleprivs->array_len); session_roleprivs = (SessionRolePrivs *) realloc((void *) session_roleprivs, size); session_roleprivs->array_len += CONTEXT_ROLEPRIVS_INCREMENT; for (i = session_roleprivs->array_len - CONTEXT_ROLEPRIVS_INCREMENT; i < session_roleprivs->array_len; i++) { session_roleprivs->context_roleprivs[i].privileges = NULL; } } else { session_roleprivs = (SessionRolePrivs *) calloc(1, CONTEXT_ROLEPRIVS_SIZE(0)); session_roleprivs->array_len = CONTEXT_ROLEPRIVS_INCREMENT; } if (!session_roleprivs) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Unable to create session memory in " "extendSessionPrivs()"))); } return session_roleprivs; } /** * Add a ContextPrivs entry to ::session_privs, from the parameters. * * @param scope_type The scope_type for the new entry * @param the scope scope for the new entry * @param roles The roles Bitmap for the new entry * @param privs The privileges Bitmap for the new entry */ static void add_scope_roleprivs(int scope_type, int scope, Bitmap *roles, Bitmap *privs) { MemoryContext old_context; int idx; if (!session_roleprivs) { session_roleprivs = extendSessionRolePrivs(NULL); } else if (session_roleprivs->active_contexts >= session_roleprivs->array_len) { session_roleprivs = extendSessionRolePrivs(session_roleprivs); } idx = session_roleprivs->active_contexts; session_roleprivs->active_contexts++; session_roleprivs->context_roleprivs[idx].scope_type = scope_type; session_roleprivs->context_roleprivs[idx].scope = scope; /* We copy the bitmaps in TopMemoryContext so they won't be * cleaned-up as transactions come and go. */ old_context = MemoryContextSwitchTo(TopMemoryContext); session_roleprivs->context_roleprivs[idx].roles = bitmapCopy(roles); session_roleprivs->context_roleprivs[idx].privileges = bitmapCopy(privs); MemoryContextSwitchTo(old_context); } /** * Update a ContextPrivs entry in ::session_privs with new roles and * privs. If there is no matching entry, we do nothing. * * @param scope_type The scope_type for the entry to be updated. * @param the scope scope for the entry to be updated. * @param roles The new roles Bitmap for the entry * @param privs The new privileges Bitmap for the entry */ static void update_scope_roleprivs(int scope_type, int scope, Bitmap *roles, Bitmap *privs) { int idx; MemoryContext old_context; findContext(&idx, scope_type, scope); if (idx == -1) { /* PK fields do not match an existing record, so the update * does nothing. */ return; } old_context = MemoryContextSwitchTo(TopMemoryContext); freeContextRolePrivs(&session_roleprivs->context_roleprivs[idx]); session_roleprivs->context_roleprivs[idx].roles = bitmapCopy(roles); session_roleprivs->context_roleprivs[idx].privileges = bitmapCopy(privs); MemoryContextSwitchTo(old_context); } /** * Predicate to indicate whether to raise an error if a privilege test * function has been called prior to a session being established. If * not, the privilege testing function should return false. The * determination of whether to error or return false is based on the * value of the veil2.system_parameter 'error on uninitialized * session' at the time that the database session is established. * * @return boolean, whether or not to raise an error. */ static bool error_if_no_session() { static bool init_done = false; static bool error = true; bool pushed; if (!init_done) { veil2_spi_connect(&pushed, "error_if_no_session() (1)"); (void) veil2_bool_from_query( "select parameter_value::boolean" " from veil2.system_parameters" " where parameter_name = 'error on uninitialized session'", 0, NULL, NULL, NULL, &error); veil2_spi_finish(pushed, "error_if_no_session (2)"); init_done = true; } return error; } /** * This is a Fetch_fn() for dealing with tuples containing 2 integers. * Its job is to populate the p_result parameter with 2 integers * from a Postgres SPI query. * * @param tuple The ::HeapTuple returned from a Postgres SPI query. * This will contain a tuple of 2 integers. * @param tupdesc The ::TupleDesc returned from the same Postgres SPI query * @param p_result Pointer to a ::tuple_2ints struct into which the 2 * integers from the SPI query will be placed. * * @return bool false, indicating to veil2_query() that * no more rows are expected. */ static bool fetch_2ints(HeapTuple tuple, TupleDesc tupdesc, void *p_result) { bool isnull; tuple_2ints *my_tup = (tuple_2ints *) p_result; my_tup->f1 = DatumGetInt32(SPI_getbinval(tuple, tupdesc, 1, &isnull)); my_tup->f2 = DatumGetInt32(SPI_getbinval(tuple, tupdesc, 2, &isnull)); return false; // No need to continue processing after this } /** * Create a temporary table used for handling privileges in become_user. * Originally, more temp tables were used but these have been replaced * by in memory structures for performance and security. */ static void create_temp_tables() { (void) veil2_query( "create temporary table veil2_ancestor_privileges" " of veil2.session_privileges_t", 0, NULL, NULL, false, NULL, NULL, NULL); } /** * Does the database donkey-work for veil2_reset_session(). * * @param clear_context Whether veil2_session_context should be * cleared as well as the privileges temp tables. */ static void do_reset_session(bool clear_context) { tuple_2ints my_tup; int processed; processed = veil2_query( "select count(*)::integer," " sum(case when c.relacl is null then 1 else 0 end)" " from pg_catalog.pg_class c" " where c.relname = 'veil2_ancestor_privileges'" " and c.relkind = 'r'" " and c.relpersistence = 't'" " and pg_catalog.pg_table_is_visible(c.oid)", 0, NULL, NULL, false, NULL, fetch_2ints, (void *) &my_tup); if (processed == 0) { /* Unexpected error in query. */ ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Temp tables query fails in veil2_reset_session()"))); } else { if (processed != 1) { /* This should be impossible. */ ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Unexpected processing error in " "veil2_reset_session: %d", processed))); } if (my_tup.f1 == 0) { /* We have no temp table, so let's create it. */ create_temp_tables(); session_ready = true; } else if (my_tup.f1 == 1) { /* We have the expected temp tables - check that access * is properly limited. */ if (my_tup.f2 != 1) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Unexpected access to temp table in " "veil2_reset_session"), errdetail("This indicates an attempt to bypass " "VPD security!"))); } /* Access to temp tables looks kosher. Truncate the * tables. */ if (clear_context) { session_context.loaded = false; } clear_session_roleprivs(); session_ready = true; } else { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Unexpected count of temp tables in " "veil2_reset_session: %d", my_tup.f1), errdetail("This indicates an attempt to bypass " "VPD security!"))); } } } /** * veil2_session_ready() returns bool * Predicate to indicate whether the current session has been properly * initialized by veil2_reset_session(). It tests the static variable * ::session_ready. * * @return bool true if this session has been set up. */ Datum veil2_session_ready(PG_FUNCTION_ARGS) { PG_RETURN_BOOL(session_ready); } /** * veil2.reset_session() returns void * * Resets a postgres session prior to the recording of session * privilege information. This ensures that the Veil2 temporary * tables, on which our security depends, exist and have not been * tamperered with. Unless this function succeeds, the privilege * testing functions veil2_i_have_global_priv(), * veil2_i_have_personal_priv(), veil2_i_have_priv_in_scope() and * veil2_i_have_priv_in_superior_scope() will always return false. * * @return void */ Datum veil2_reset_session(PG_FUNCTION_ARGS) { bool pushed; session_ready = false; veil2_spi_connect(&pushed, "failed to reset session (1)"); do_reset_session(true); veil2_spi_finish(pushed, "failed to reset session (2)"); PG_RETURN_VOID(); } /** * veil2.reset_session_privs() returns void * * Clears the temp table and cached privileges for a postgres * session and reloads them. * * @return void */ Datum veil2_reset_session_privs(PG_FUNCTION_ARGS) { bool pushed; veil2_spi_connect(&pushed, "failed to reset session privs (1)"); do_reset_session(false); veil2_spi_finish(pushed, "failed to reset session privs (2)"); PG_RETURN_VOID(); } /** * veil2.session_context() * * Optionally set (if variables are provided), and return the session * context. * * @return record */ Datum veil2_session_context(PG_FUNCTION_ARGS) { Datum results[9]; static bool allnulls[9] = {true, true, true, true, true, true, true, true, true}; static bool nonulls[9] = {false, false, false, false, false, false, false, false, false}; bool *nulls; TupleDesc tuple_desc; HeapTuple tuple; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); } tuple_desc = BlessTupleDesc(tuple_desc); if (!PG_ARGISNULL(0)) { session_context.accessor_id = PG_GETARG_INT32(0); session_context.session_id = PG_GETARG_INT64(1); session_context.login_context_type_id = PG_GETARG_INT32(2); session_context.login_context_id = PG_GETARG_INT32(3); session_context.session_context_type_id = PG_GETARG_INT32(4); session_context.session_context_id = PG_GETARG_INT32(5); session_context.mapping_context_type_id = PG_GETARG_INT32(6); session_context.mapping_context_id = PG_GETARG_INT32(7); if (PG_ARGISNULL(8)) { session_context.parent_session_id = session_context.session_id; } else { session_context.parent_session_id = PG_GETARG_INT64(8); } session_context.loaded = true; } if (session_context.loaded) { results[0] = Int32GetDatum(session_context.accessor_id); results[1] = Int64GetDatum(session_context.session_id); results[2] = Int32GetDatum(session_context.login_context_type_id); results[3] = Int32GetDatum(session_context.login_context_id); results[4] = Int32GetDatum(session_context.session_context_type_id); results[5] = Int32GetDatum(session_context.session_context_id); results[6] = Int32GetDatum(session_context.mapping_context_type_id); results[7] = Int32GetDatum(session_context.mapping_context_id); nulls = nonulls; if (session_context.parent_session_id == session_context.session_id) { nulls[8] = true; } else { results[8] = Int64GetDatum(session_context.parent_session_id); nulls[8] = false; } } else { nulls = allnulls; } tuple = heap_form_tuple(tuple_desc, results, nulls); tuple_desc = BlessTupleDesc(tuple_desc); return HeapTupleGetDatum(tuple); } /** * veil2.session_privileges() * * Return the current in-memory session_privileges as though they were * a SQL table. * * @return setof record */ Datum veil2_session_privileges(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; AttInMetadata *attinmeta; MemoryContext oldcontext; TupleDesc tupdesc; long idx; bool nulls[4] = {false, false, false, false}; if (SRF_IS_FIRSTCALL()) { funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); tupdesc = RelationNameGetTupleDesc("veil2.session_privileges_t"); funcctx->tuple_desc = tupdesc; attinmeta = TupleDescGetAttInMetadata(tupdesc); funcctx->attinmeta = attinmeta; MemoryContextSwitchTo(oldcontext); /* We use the user function context to store an index into * session_roleprivs. */ funcctx->user_fctx = (void *) 0; } funcctx = SRF_PERCALL_SETUP(); idx = (long) funcctx->user_fctx; if (session_roleprivs && (idx < session_roleprivs->active_contexts)) { Datum results[4]; HeapTuple tuple; Datum datum; Bitmap *bitmap; results[0] = Int32GetDatum(session_roleprivs-> context_roleprivs[idx].scope_type); results[1] = Int32GetDatum(session_roleprivs-> context_roleprivs[idx].scope); bitmap = session_roleprivs->context_roleprivs[idx].roles; oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); if (bitmap) { results[2] = (Datum) bitmapCopy(bitmap); } else { nulls[2] = true; } bitmap = session_roleprivs->context_roleprivs[idx].privileges; if (bitmap) { results[3] = (Datum) bitmapCopy(bitmap); } else { nulls[3] = true; } MemoryContextSwitchTo(oldcontext); tupdesc = funcctx->tuple_desc; tuple = heap_form_tuple(tupdesc, results, nulls); tupdesc = BlessTupleDesc(tupdesc); datum = TupleGetDatum(tupdesc, tuple); funcctx->user_fctx = (void *) (idx + 1); SRF_RETURN_NEXT(funcctx, datum); } else { SRF_RETURN_DONE(funcctx); } } /** * veil2.add_session_privileges(scope_type_id, scope_id, * roles, privileges) * * Create a new in-memory session_privileges record. Note that this * *must* be called with records ordered by scope_type_id, and * scope_id. This is because we use a binary search to match the * relevant scope when "querying" this structure internally. * @param integer scope_type_id The type of scope * @param integer scope_id The id of the actual scope * @param Bitmap roles The roles assigned in the context for this scope * @param Bitmap privs The privileges that apply in this scope * @return void */ Datum veil2_add_session_privileges(PG_FUNCTION_ARGS) { int scope_type_id = PG_GETARG_INT32(0); int scope_id = PG_GETARG_INT32(1); Bitmap *roles = PG_GETARG_BITMAP(2); Bitmap *privs = PG_GETARG_BITMAP(3); add_scope_roleprivs(scope_type_id, scope_id, roles, privs); PG_RETURN_VOID(); } /** * veil2.update_session_privileges(scope_type_id, scope_id, * roles, privileges) * * Update an in-memory session_privileges record, with new roles and * prvs bitmaps. * @param integer scope_type_id The type of scope * @param integer scope_id The id of the actual scope * @param Bitmap roles The roles assigned in the context for this scope * @param Bitmap privs The privileges that apply in this scope * @return void */ Datum veil2_update_session_privileges(PG_FUNCTION_ARGS) { int scope_type_id = PG_GETARG_INT32(0); int scope_id = PG_GETARG_INT32(1); Bitmap *roles = PG_GETARG_BITMAP(2); Bitmap *privs = PG_GETARG_BITMAP(3); update_scope_roleprivs(scope_type_id, scope_id, roles, privs); PG_RETURN_VOID(); } /** * veil2.true(params) returns bool * * Always return true, regardless of parameters. This is used to * determine the minimum possible overhead for a privilege testing * predicate, for performance measurements. * * @return boolean true */ Datum veil2_true(PG_FUNCTION_ARGS) { return true; } /** * Check whether a session has been properly initialized. If not, and * we are supposed to fail in such a situation, fail with an appropriate * error message. Otherwise return true if the session is ready to * go. * * @result boolean True if our session has been properly initialized. */ static bool checkSessionReady() { if (session_ready) { return true; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } return false; } /** * veil2.i_have_global_priv(priv) returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, with global scope. * * @param privilege_id Integer giving privilege to test for * * @return boolean true if the session has the given privilege */ Datum veil2_i_have_global_priv(PG_FUNCTION_ARGS) { static int context_idx = -1; int priv = PG_GETARG_INT32(0); bool result; if ((result = checkSessionReady())) { result = checkContext(&context_idx, 1, 0, priv); } result_counts[result]++; return result; } /** * veil2.i_have_personal_priv(priv, accessor_id) returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in their personal scope (ie for data * pertaining to themselves). * * @param privilege_id Integer giving privilege to test for * @param accessor_id Integer id for a party from the record being * checked. * * @return boolean true if the session has the given privilege in the * personal scope of the given accessor_id */ Datum veil2_i_have_personal_priv(PG_FUNCTION_ARGS) { static int context_idx = -1; bool result; int priv = PG_GETARG_INT32(0); int accessor_id = PG_GETARG_INT32(1); if ((result = checkSessionReady())) { result = checkContext(&context_idx, 2, accessor_id, priv); } result_counts[result]++; return result; } /** * veil2.i_have_priv_in_scope(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in a specific scope * (scope_type_id, scope_id). * * @param privilege_id Integer giving privilege to test for * @param scope_type_id Integer id of the scope type to be checked * @param scope_id Integer id of the scop to be checked * * @return boolean true if the session has the given privilege for the * given scope_type_id and scope_id */ Datum veil2_i_have_priv_in_scope(PG_FUNCTION_ARGS) { static int context_idx = -1; bool result; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); if ((result = checkSessionReady())) { result = checkContext(&context_idx, scope_type_id, scope_id, priv); } result_counts[result]++; return result; } /** * veil2.i_have_priv_in_scope_or_global(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in a specific scope * (scope_type_id, scope_id), or in global scope. * * @param privilege_id Integer giving privilege to test for * @param scope_type_id Integer id of the scope type to be checked * @param scope_id Integer id of the scop to be checked * * @return boolean true if the session has the given privilege for the * given scope_type_id and scope_id */ Datum veil2_i_have_priv_in_scope_or_global(PG_FUNCTION_ARGS) { static int global_context_idx = -1; static int given_context_idx = -1; bool result; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); if ((result = checkSessionReady())) { result = (checkContext(&global_context_idx, 1, 0, priv) || checkContext(&given_context_idx, scope_type_id, scope_id, priv)); } result_counts[result]++; return result; } /** * veil2.i_have_priv_in_superior_scope(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in a superior scope to that supplied: * scope_type_id, scope_id. * * @param privilege_id Integer giving privilege to test for * @param scope_type_id Integer id of the scope type to be checked * @param scope_id Integer id of the scop to be checked * * @return boolean true if the session has the given privilege in a * scope superior to that given by scope_type_id and scope_id */ Datum veil2_i_have_priv_in_superior_scope(PG_FUNCTION_ARGS) { static void *saved_plan = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; if ((result = checkSessionReady())) { veil2_spi_connect(&pushed, "SPI connect failed in " "veil2_i_have_priv_in_superior_scope()"); found = veil2_bool_from_query( "select true" " from veil2.all_superior_scopes asp" " inner join veil2.session_privileges() sp" " on sp.scope_type_id = asp.superior_scope_type_id" " and sp.scope_id = asp.superior_scope_id" " where asp.scope_type_id = $2" " and asp.scope_id = $3" " and sp.privs ? $1", 3, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_superior_scope()"); result = found && result; } result_counts[result]++; return result; } /** * veil2.i_have_priv_in_scope_or_superior(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in the supplied scope or a superior one: * scope_type_id, scope_id. * * @param privilege_id Integer giving privilege to test for * @param scope_type_id Integer id of the scope type to be checked * @param scope_id Integer id of the scop to be checked * * @return boolean true if the session has the given privilege in the * scope given by scope_type_id and scope_id or a supeior one. */ Datum veil2_i_have_priv_in_scope_or_superior(PG_FUNCTION_ARGS) { static int context_idx = -1; static void *saved_plan = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; if ((result = checkSessionReady())) { /* Start by checking priv in scope - this can maybe save us a * query. */ result = checkContext(&context_idx, scope_type_id, scope_id, priv); if (!result) { veil2_spi_connect(&pushed, "SPI connect failed in " "veil2_i_have_priv_in_scope_or_superior()"); found = veil2_bool_from_query( "select true" " from veil2.all_superior_scopes asp" " inner join veil2.session_privileges() sp" " on sp.scope_type_id = asp.superior_scope_type_id" " and sp.scope_id = asp.superior_scope_id" " where asp.scope_type_id = $2" " and asp.scope_id = $3" " and sp.privs ? $1", 3, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_scope_or_superior()"); result = found && result; } } result_counts[result]++; return result; } /** * veil2.i_have_priv_in_scope_or_superior_or_global(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in global_scope, or the supplied * scope, or a superior one: * scope_type_id, scope_id. * * @param privilege_id Integer giving privilege to test for * @param scope_type_id Integer id of the scope type to be checked * @param scope_id Integer id of the scop to be checked * * @return boolean true if the session has the given privilege in the * scope given by scope_type_id and scope_id or a supeior one or * global scope. */ Datum veil2_i_have_priv_in_scope_or_superior_or_global(PG_FUNCTION_ARGS) { static int global_context_idx = -1; static int given_context_idx = -1; static void *saved_plan = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; if ((result = checkSessionReady())) { result = (checkContext(&global_context_idx, 1, 0, priv) || checkContext(&given_context_idx, scope_type_id, scope_id, priv)); if (!result) { veil2_spi_connect(&pushed, "SPI connect failed in " "veil2_i_have_priv_in_scope_or_superior()"); found = veil2_bool_from_query( "select true" " from veil2.all_superior_scopes asp" " inner join veil2.session_privileges() sp" " on sp.scope_type_id = asp.superior_scope_type_id" " and sp.scope_id = asp.superior_scope_id" " where asp.scope_type_id = $2" " and asp.scope_id = $3" " and sp.privs ? $1", 3, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_scope_or_superior()"); result = found && result; } } result_counts[result]++; return result; } /** * Return the number of times one of the i_have_privilege_xxxx() * functions has returned false and true. * * @return Record: false_count, true_count */ Datum veil2_result_counts(PG_FUNCTION_ARGS) { /* We only return positive integers. That's just the way it * is. */ Datum results[2] = {Int32GetDatum(result_counts[0] & INT_MAX), Int32GetDatum(result_counts[1] & INT_MAX)}; bool nulls[2] = {false, false}; TupleDesc tuple_desc; HeapTuple tuple; if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); } tuple_desc = BlessTupleDesc(tuple_desc); tuple = heap_form_tuple(tuple_desc, results, nulls); return HeapTupleGetDatum(tuple); } /** * Create a dynamically allocated text value as a copy of a C string. * * @param in String to be copied * * @return Dynamically allocated (by palloc()) copy of in. */ static text * textfromstr(char *in) { int len = strlen(in); text *out = palloc(len + VARHDRSZ); memcpy(VARDATA(out), in, len); SET_VARSIZE(out, (len + VARHDRSZ)); return out; } /** * Provide the path to where documentation should be stored on the server. * * @return Text value containing the path. */ Datum veil2_docpath(PG_FUNCTION_ARGS) { PG_RETURN_TEXT_P(textfromstr(DOCS_PATH)); } /** * Provide the path to where veil2 sql scripts should be stored on the * server. * * @return Text value containing the path. */ Datum veil2_datapath(PG_FUNCTION_ARGS) { PG_RETURN_TEXT_P(textfromstr(DATA_PATH)); } /** * Provide the veil2 version as a string. * * @return Text value containing the version. */ Datum veil2_version(PG_FUNCTION_ARGS) { PG_RETURN_TEXT_P(textfromstr(VEIL2_VERSION)); }