/** * @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_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); /** * 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 *privileges; } ContextPrivs; /** * 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; ContextPrivs context_privs[0]; } SessionPrivs; /** * The SessionPrivs object for this session. */ static SessionPrivs *session_privs = NULL; /** * Whether we have loaded our session's ContextPrivs into session memory. */ static bool session_privs_loaded = false; /** * Locate a particular ContextPriv entry in ::session_privs. * * @param p_idx Pointer to a cached index value for the entry in the * ::session_privs->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 = session_privs->active_contexts - 1; ContextPrivs *this_cp; 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_privs->context_privs[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_privs->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_privs->context_privs[*p_idx].privileges, priv); } /** * Free a ContextPrivs entry. This just means freeing the privileges * Bitmap and zeroing the pointer for it. * * @param cp The ContextPrivs entry to be cleared out. */ static void freeContextPrivs(ContextPrivs *cp) { pfree((void *) cp->privileges); cp->privileges = NULL; } /** * Clear all ContextPrivs entries in session_privs. */ static void clear_session_privs() { int i; if (session_privs) { for (i = session_privs->active_contexts - 1; i >= 0; i--) { freeContextPrivs(&session_privs->context_privs[i]); } session_privs->active_contexts = 0; session_privs_loaded = false; } } /** * How many ContextPrivs entries a SessionPrivs structure will be * created with/extended by. */ #define CONTEXT_PRIVS_INCREMENT 16 /** Provide the size that we want our SessionPrivs structure to be. * * @param elems the number of ContextPrivs entries already in place. This * will be increased by CONTEXT_PRIVS_INCREMENT. */ #define CONTEXT_PRIVS_SIZE(elems) ( \ sizeof(SessionPrivs) + \ (sizeof(ContextPrivs) * \ (elems + CONTEXT_PRIVS_INCREMENT))) /* * Create or extend our SessionPrivs structure. * * @result The newly allocated SessionPrivs struct. */ static SessionPrivs * extendSessionPrivs(SessionPrivs *session_privs) { size_t size; int i; if (session_privs) { size = CONTEXT_PRIVS_SIZE(session_privs->array_len); session_privs = (SessionPrivs *) realloc((void *) session_privs, size); session_privs->array_len += CONTEXT_PRIVS_INCREMENT; for (i = session_privs->array_len - CONTEXT_PRIVS_INCREMENT; i < session_privs->array_len; i++) { session_privs->context_privs[i].privileges = NULL; } } else { session_privs = (SessionPrivs *) calloc(1, CONTEXT_PRIVS_SIZE(0)); session_privs->array_len = CONTEXT_PRIVS_INCREMENT; } if (!session_privs) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Unable to create session memory in " "extendSessionPrivs()"))); } return session_privs; } /** * 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 privs The privileges Bitmap for the new entry */ static void add_scope_privs(int scope_type, int scope, Bitmap *privs) { MemoryContext old_context; int idx = session_privs->active_contexts; if (session_privs->active_contexts >= session_privs->array_len) { session_privs = extendSessionPrivs(session_privs); } session_privs->active_contexts++; session_privs->context_privs[idx].scope_type = scope_type; session_privs->context_privs[idx].scope = scope; /* We copy the bitmap in TopMemoryContext so that it won't be * cleaned-up as transactions come and go. */ old_context = MemoryContextSwitchTo(TopMemoryContext); session_privs->context_privs[idx].privileges = bitmapCopy(privs); MemoryContextSwitchTo(old_context); } /** * A ::Fetch_fn for veil2_query() that retrieves the details for a * ContextPrivs entry and adds it to ::session_privs using * add_scope_privs(). * * @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 This should be null. * * @result true, to indicate to veil2_query() that there may be more * records to fetch. */ static bool fetch_scope_privs(HeapTuple tuple, TupleDesc tupdesc, void *p_result) { bool isnull; int scope_type; int scope; Bitmap *privs; scope_type = DatumGetInt32(SPI_getbinval(tuple, tupdesc, 1, &isnull)); scope = DatumGetInt32(SPI_getbinval(tuple, tupdesc, 2, &isnull)); privs = DatumGetBitmap(SPI_getbinval(tuple, tupdesc, 3, &isnull)); add_scope_privs(scope_type, scope, privs); return true; } /** * Does the donkey-work of loading session privileges into session * memory. */ static void do_load_session_privs() { bool pushed; if (session_privs) { clear_session_privs(); } else { session_privs = extendSessionPrivs(NULL); } veil2_spi_connect(&pushed, "SPI connect failed in do_load_session_privs() - veil2"); (void) veil2_query( "select scope_type_id, scope_id, privs" " from veil2_session_privileges" " order by 1, 2", 0, NULL, NULL, false, NULL, fetch_scope_privs, NULL); veil2_spi_finish(pushed, "SPI finish failed in do_load_session_privs() - veil2"); } /** * Manage the conditional loading of session privileges into session * memory. If the session is already loaded, it does nothing. */ static void load_privs() { if (!session_privs_loaded) { do_load_session_privs(); session_privs_loaded = true; } } /** * 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 the temporary tables used for recording session privileges * and context. */ static void create_temp_tables() { (void) veil2_query( "create temporary table veil2_session_privileges" " of veil2.session_privileges_t", 0, NULL, NULL, false, NULL, NULL, NULL); (void) veil2_query( "create temporary table veil2_ancestor_privileges" " of veil2.session_privileges_t", 0, NULL, NULL, false, NULL, NULL, NULL); (void) veil2_query( "create temporary table veil2_session_context" " of veil2.session_context_t", 0, NULL, NULL, false, NULL, NULL, NULL); } /** * Truncate the veil2_session_privileges and veil2_session_context * temporary tables (actually we use deletion rather than truncation * as it seems faster. * * @param clear_context Whether veil2_session_context should be * cleared as well as the privileges temp tables. */ static void truncate_temp_tables(bool clear_context) { (void) veil2_query( "delete from veil2_session_privileges", 0, NULL, NULL, false, NULL, NULL, NULL); if (clear_context) { (void) veil2_query( "delete from veil2_session_context", 0, NULL, NULL, false, NULL, NULL, NULL); } } /** * 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); } /** * 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 in ('veil2_session_privileges'," " 'veil2_session_context'," " '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 tables, so let's create them. */ create_temp_tables(); session_ready = true; } else if (my_tup.f1 == 3) { /* We have the expected temp tables - check that access * is properly limited. */ if (my_tup.f2 != 3) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Unexpected access to temp tables in " "veil2_reset_session"), errdetail("This indicates an attempt to bypass " "VPD security!"))); } /* Access to temp tables looks kosher. Truncate the * tables. */ truncate_temp_tables(clear_context); 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.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; clear_session_privs(); 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; clear_session_privs(); 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.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())) { load_privs(); 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())) { load_privs(); 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())) { load_privs(); 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())) { load_privs(); 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())) { load_privs(); /* 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())) { load_privs(); 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)); }