8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-27 18:03:04 +01:00
firebird-mirror/src/jrd/scl.epp

1784 lines
46 KiB
Plaintext

/*
* PROGRAM: JRD Access Method
* MODULE: scl.epp
* DESCRIPTION: Security class handler
*
* The contents of this file are subject to the Interbase Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy
* of the License at http://www.Inprise.com/IPL.html
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
* or implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code was created by Inprise Corporation
* and its predecessors. Portions created by Inprise Corporation are
* Copyright (C) Inprise Corporation.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
* 2001.6.12 Claudio Valderrama: the role should be wiped out if invalid.
* 2001.8.12 Claudio Valderrama: Squash security bug when processing
* identifiers with embedded blanks: check_procedure, check_relation
* and check_string, the latter being called from many places.
*
*/
#include "firebird.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "../jrd/jrd.h"
#include "../jrd/ods.h"
#include "../jrd/scl.h"
#include "../jrd/acl.h"
#include "../jrd/blb.h"
#include "../jrd/irq.h"
#include "../jrd/obj.h"
#include "../jrd/req.h"
#include "../jrd/tra.h"
#include "../common/gdsassert.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/ini_proto.h"
#include "../yvalve/gds_proto.h"
#include "../common/isc_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/grant_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/constants.h"
#include "fb_exception.h"
#include "../common/utils_proto.h"
#include "../common/classes/array.h"
#include "../common/config/config.h"
#include "../common/os/os_utils.h"
#include "../common/classes/ClumpletWriter.h"
#include "../jrd/PreparedStatement.h"
#include "../jrd/ResultSet.h"
const int UIC_BASE = 10;
using namespace Jrd;
using namespace Firebird;
DATABASE DB = FILENAME "ODS.RDB";
static bool check_number(const UCHAR*, USHORT);
static bool check_user_group(thread_db* tdbb, const UCHAR*, USHORT);
static bool check_string(const UCHAR*, const Firebird::MetaName&);
static SecurityClass::flags_t compute_access(thread_db* tdbb, const SecurityClass*,
const jrd_rel*, SLONG, const Firebird::MetaName&);
static SecurityClass::flags_t walk_acl(thread_db* tdbb, const Acl&, const jrd_rel*,
SLONG, const Firebird::MetaName&);
static void raiseError(SecurityClass::flags_t mask, SLONG type, const Firebird::MetaName& name,
const Firebird::MetaName& r_name);
namespace
{
struct P_NAMES
{
SecurityClass::flags_t p_names_priv;
USHORT p_names_acl;
const TEXT* p_names_string;
};
const P_NAMES p_names[] =
{
{ SCL_alter, priv_alter, "ALTER" },
{ SCL_control, priv_control, "CONTROL" },
{ SCL_drop, priv_drop, "DROP" },
{ SCL_insert, priv_insert, "INSERT" },
{ SCL_update, priv_update, "UPDATE" },
{ SCL_delete, priv_delete, "DELETE" },
{ SCL_select, priv_select, "SELECT" },
{ SCL_references, priv_references, "REFERENCES" },
{ SCL_execute, priv_execute, "EXECUTE" },
{ SCL_usage, priv_usage, "USAGE" },
{ SCL_create, priv_create, "CREATE" },
{ 0, 0, "" }
};
// Database is never requested through CMP_post_access.
const char* const object_str_database = "DATABASE";
//const char* const object_str_schema = "SCHEMA";
const char* const object_str_table = "TABLE";
const char* const object_str_package = "PACKAGE";
const char* const object_str_procedure = "PROCEDURE";
const char* const object_str_function = "FUNCTION";
const char* const object_str_column = "COLUMN";
const char* const object_str_charset = "CHARACTER SET";
const char* const object_str_collation = "COLLATION";
const char* const object_str_domain = "DOMAIN";
const char* const object_str_exception = "EXCEPTION";
const char* const object_str_generator = "GENERATOR";
const char* const object_str_view = "VIEW";
const char* const object_str_role = "ROLE";
const char* const object_str_filter = "FILTER";
struct SecObjectNamePriority
{
const char* name;
SLONG num;
};
const SecObjectNamePriority priorities[] =
{
{object_str_database, SCL_object_database},
//{object_str_schema, SCL_object_schema},
{object_str_table, SCL_object_table},
{object_str_package, SCL_object_package},
{object_str_procedure, SCL_object_procedure},
{object_str_function, SCL_object_function},
{object_str_column, SCL_object_column},
{object_str_charset, SCL_object_charset},
{object_str_collation, SCL_object_collation},
{object_str_domain, SCL_object_domain},
{object_str_exception, SCL_object_exception},
{object_str_generator, SCL_object_generator},
{object_str_view, SCL_object_view},
{object_str_role, SCL_object_role},
{object_str_filter, SCL_object_filter},
{"", 0}
};
/* Unused, may be needed
SLONG accTypeStrToNum(const char* name)
{
for (const SecObjectNamePriority* p = priorities; p->num != 0; ++p)
{
if (strcmp(p->name, name) == 0)
return p->num;
}
fb_assert(false);
return 0;
}
*/
const char* accTypeNumToStr(const SLONG num)
{
for (const SecObjectNamePriority* p = priorities; p->num != 0; ++p)
{
if (p->num == num)
return p->name;
}
fb_assert(false);
return "<unknown object type>";
}
} // anonymous namespace
static void raiseError(SecurityClass::flags_t mask, SLONG type, const Firebird::MetaName& name,
const Firebird::MetaName& r_name)
{
const P_NAMES* names;
for (names = p_names; names->p_names_priv; names++)
{
if (names->p_names_priv & mask)
break;
}
const char* const typeAsStr = accTypeNumToStr(type);
const Firebird::string fullName = r_name.hasData() ?
r_name.c_str() + Firebird::string(".") + name.c_str() : name.c_str();
ERR_post(Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
Arg::Str(typeAsStr) <<
Arg::Str(fullName));
}
void SCL_check_access(thread_db* tdbb,
const SecurityClass* s_class,
SLONG view_id,
SLONG obj_type,
const Firebird::MetaName& obj_name,
SecurityClass::flags_t mask,
SLONG type,
bool recursive,
const Firebird::MetaName& name,
const Firebird::MetaName& r_name)
{
/**************************************
*
* S C L _ c h e c k _ a c c e s s
*
**************************************
*
* Functional description
* Check security class for desired permission.
*
**************************************/
SET_TDBB(tdbb);
if (tdbb->tdbb_flags & TDBB_trusted_ddl)
return;
if (s_class && (s_class->scl_flags & SCL_corrupt))
{
ERR_post(Arg::Gds(isc_no_priv) << Arg::Str("(ACL unrecognized)") <<
Arg::Str("security_class") <<
Arg::Str(s_class->scl_name));
}
// Check global DDL permissions with ANY option which allow user to make changes non owned objects
const SecurityClass::flags_t obj_mask = SCL_get_object_mask(type);
if (mask & obj_mask)
return;
if (!s_class || (mask & s_class->scl_flags))
return;
const jrd_rel* view = NULL;
if (view_id)
view = MET_lookup_relation_id(tdbb, view_id, false);
if ((view || obj_name.hasData()) &&
(compute_access(tdbb, s_class, view, obj_type, obj_name) & mask))
{
return;
}
// Allow recursive procedure/function call
if (recursive &&
((type == SCL_object_procedure && obj_type == id_procedure) ||
(type == SCL_object_function && obj_type == id_function)) &&
obj_name == name)
{
return;
}
raiseError(mask, type, name, r_name);
}
void SCL_check_create_access(thread_db* tdbb, int type)
{
/**************************************
*
* S C L _ c h e c k _ c r e a t e _ a c c e s s
*
**************************************
*
* Functional description
* Check create access on a database object (DDL access)
*
**************************************/
SET_TDBB(tdbb);
const SecurityClass::flags_t obj_mask = SCL_get_object_mask(type);
if (!(obj_mask & SCL_create))
ERR_post(Arg::Gds(isc_dyn_no_priv));
}
void SCL_check_charset(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ c h a r s e t
*
**************************************
*
* Functional description
* Given a character set name, check for a set of privileges.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_cs_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
CS IN RDB$CHARACTER_SETS
WITH CS.RDB$CHARACTER_SET_NAME EQ name.c_str()
{
if (!CS.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, CS.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, name, mask, SCL_object_charset, false, name);
}
void SCL_check_collation(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ c o l l a t i o n
*
**************************************
*
* Functional description
* Given a collation name, check for a set of privileges.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_coll_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
COLL IN RDB$COLLATIONS
WITH COLL.RDB$COLLATION_NAME EQ name.c_str()
{
if (!COLL.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, COLL.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, name, mask, SCL_object_collation, false, name);
}
void SCL_check_database(thread_db* tdbb, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ d a t a b a s e
*
**************************************
*
* Functional description
* Check for a set of privileges of current database.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* const att_class = attachment->att_security_class;
if (att_class && (att_class->scl_flags & mask))
return;
if (mask == SCL_alter && attachment->locksmith(tdbb, USE_NBACKUP_UTILITY))
return;
const P_NAMES* names;
for (names = p_names; names->p_names_priv; names++)
{
if (names->p_names_priv & mask)
break;
}
ERR_post(Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
Arg::Str(object_str_database) <<
Arg::Str(""));
}
void SCL_check_domain(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ d o m a i n
*
**************************************
*
* Functional description
* Given a domain name, check for a set of privileges.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_gfld_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
FLD IN RDB$FIELDS
WITH FLD.RDB$FIELD_NAME EQ name.c_str()
{
if (!FLD.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, FLD.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, name, mask, SCL_object_domain, false, name);
}
void SCL_check_exception(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ e x c e p t i o n
*
**************************************
*
* Functional description
* Given an exception name, check for a set of privileges.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_exc_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
XCP IN RDB$EXCEPTIONS
WITH XCP.RDB$EXCEPTION_NAME EQ name.c_str()
{
if (!XCP.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, XCP.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, name, mask, SCL_object_exception, false, name);
}
void SCL_check_generator(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ g e n e r a t o r
*
**************************************
*
* Functional description
* Given a generator name, check for a set of privileges.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_gen_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
GEN IN RDB$GENERATORS
WITH GEN.RDB$GENERATOR_NAME EQ name.c_str()
{
if (!GEN.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, GEN.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, name, mask, SCL_object_generator, false, name);
}
void SCL_check_index(thread_db* tdbb, const Firebird::MetaName& index_name, UCHAR index_id,
SecurityClass::flags_t mask)
{
/******************************************************
*
* S C L _ c h e c k _ i n d e x
*
******************************************************
*
* Functional description
* Given a index name (as a TEXT), check for a
* set of privileges on the table that the index is on and
* on the fields involved in that index.
* CVC: Allow the same function to use the zero-based index id, too.
* The idx.idx_id value is zero based but system tables use
* index id's being one based, hence adjust the incoming value
* before calling this function. If you use index_id, index_name
* becomes relation_name since index ids are relative to tables.
*
*******************************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
const SecurityClass* default_s_class = NULL;
// No security to check for if the index is not yet created
if ((index_name.length() == 0) && (index_id < 1)) {
return;
}
Firebird::MetaName reln_name, aux_idx_name;
const Firebird::MetaName* idx_name_ptr = &index_name;
const Firebird::MetaName* relation_name_ptr = &index_name;
AutoRequest request;
int systemFlag = 0;
// No need to cache this request handle, it's only used when
// new constraints are created
if (index_id < 1)
{
FOR(REQUEST_HANDLE request) IND IN RDB$INDICES
CROSS REL IN RDB$RELATIONS
OVER RDB$RELATION_NAME
WITH IND.RDB$INDEX_NAME EQ index_name.c_str()
{
reln_name = REL.RDB$RELATION_NAME;
if (!REL.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
if (!REL.RDB$DEFAULT_CLASS.NULL)
default_s_class = SCL_get_class(tdbb, REL.RDB$DEFAULT_CLASS);
systemFlag = REL.RDB$SYSTEM_FLAG;
}
END_FOR
}
else
{
idx_name_ptr = &aux_idx_name;
FOR (REQUEST_HANDLE request) IND IN RDB$INDICES
CROSS REL IN RDB$RELATIONS
OVER RDB$RELATION_NAME
WITH IND.RDB$RELATION_NAME EQ relation_name_ptr->c_str()
AND IND.RDB$INDEX_ID EQ index_id
{
reln_name = REL.RDB$RELATION_NAME;
aux_idx_name = IND.RDB$INDEX_NAME;
if (!REL.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
if (!REL.RDB$DEFAULT_CLASS.NULL)
default_s_class = SCL_get_class(tdbb, REL.RDB$DEFAULT_CLASS);
systemFlag = REL.RDB$SYSTEM_FLAG;
}
END_FOR
}
if (systemFlag == 1 && !attachment->isRWGbak())
{
// Someone is going to reference system table in FK
// Usually it's not good idea
raiseError(mask, SCL_object_table, reln_name, "");
}
// Check if the relation exists. It may not have been created yet.
// Just return in that case.
if (reln_name.isEmpty())
return;
SCL_check_access(tdbb, s_class, 0, 0, NULL, mask, SCL_object_table, false, reln_name);
request.reset();
// Check if the field used in the index has the appropriate
// permission. If the field in question does not have a security class
// defined, then the default security class for the table applies for that
// field.
// No need to cache this request handle, it's only used when
// new constraints are created
FOR(REQUEST_HANDLE request) ISEG IN RDB$INDEX_SEGMENTS
CROSS RF IN RDB$RELATION_FIELDS
OVER RDB$FIELD_NAME
WITH RF.RDB$RELATION_NAME EQ reln_name.c_str()
AND ISEG.RDB$INDEX_NAME EQ idx_name_ptr->c_str()
{
s_class = (!RF.RDB$SECURITY_CLASS.NULL) ?
SCL_get_class(tdbb, RF.RDB$SECURITY_CLASS) : default_s_class;
SCL_check_access(tdbb, s_class, 0, 0, NULL, mask,
SCL_object_column, false, RF.RDB$FIELD_NAME, reln_name);
}
END_FOR
}
void SCL_check_package(thread_db* tdbb, const dsc* dsc_name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ p a c k a g e
*
**************************************
*
* Functional description
* Given a package name, check for a set of privileges. The
* package in question may or may not have been created, let alone
* scanned. This is used exclusively for meta-data operations.
*
**************************************/
SET_TDBB(tdbb);
// Get the name in CSTRING format, ending on NULL or SPACE
fb_assert(dsc_name->dsc_dtype == dtype_text);
const Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
dsc_name->dsc_length);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_pkg_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
PKG IN RDB$PACKAGES
WITH PKG.RDB$PACKAGE_NAME EQ name.c_str()
{
if (!PKG.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, PKG.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, id_package, name, mask, SCL_object_package, false, name);
}
void SCL_check_procedure(thread_db* tdbb, const dsc* dsc_name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ p r o c e d u r e
*
**************************************
*
* Functional description
* Given a procedure name, check for a set of privileges. The
* procedure in question may or may not have been created, let alone
* scanned. This is used exclusively for meta-data operations.
*
**************************************/
SET_TDBB(tdbb);
// Get the name in CSTRING format, ending on NULL or SPACE
fb_assert(dsc_name->dsc_dtype == dtype_text);
const Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
dsc_name->dsc_length);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_p_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
SPROC IN RDB$PROCEDURES
WITH SPROC.RDB$PROCEDURE_NAME EQ name.c_str() AND
SPROC.RDB$PACKAGE_NAME MISSING
{
if (!SPROC.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, SPROC.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, id_procedure, name, mask, SCL_object_procedure, false, name);
}
void SCL_check_function(thread_db* tdbb, const dsc* dsc_name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ f u n c t i o n
*
**************************************
*
* Functional description
* Given a function name, check for a set of privileges. The
* function in question may or may not have been created, let alone
* scanned. This is used exclusively for meta-data operations.
*
**************************************/
SET_TDBB(tdbb);
// Get the name in CSTRING format, ending on NULL or SPACE
fb_assert(dsc_name->dsc_dtype == dtype_text);
const Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
dsc_name->dsc_length);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_f_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
SFUN IN RDB$FUNCTIONS
WITH SFUN.RDB$FUNCTION_NAME EQ name.c_str() AND
SFUN.RDB$PACKAGE_NAME MISSING
{
if (!SFUN.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, SFUN.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, id_function, name, mask, SCL_object_function, false, name);
}
void SCL_check_filter(thread_db* tdbb, const MetaName &name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ f i l t e r
*
**************************************
*
* Functional description
* Given a filter name, check for a set of privileges. The
* filter in question may or may not have been created, let alone
* scanned. This is used exclusively for meta-data operations.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_f_security, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request)
F IN RDB$FILTERS
WITH F.RDB$FUNCTION_NAME EQ name.c_str()
{
if (!F.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, F.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, id_filter, name, mask, SCL_object_filter, false, name);
}
void SCL_check_relation(thread_db* tdbb, const dsc* dsc_name, SecurityClass::flags_t mask,
bool protectSys)
{
/**************************************
*
* S C L _ c h e c k _ r e l a t i o n
*
**************************************
*
* Functional description
* Given a relation name, check for a set of privileges. The
* relation in question may or may not have been created, let alone
* scanned. This is used exclusively for meta-data operations.
*
**************************************/
SET_TDBB(tdbb);
// Get the name in CSTRING format, ending on NULL or SPACE
fb_assert(dsc_name->dsc_dtype == dtype_text);
const Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
dsc_name->dsc_length);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_v_security_r, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ name.c_str()
{
if (protectSys && REL.RDB$SYSTEM_FLAG == 1 && !attachment->isRWGbak())
{
// Someone is going to modify system table layout
// Usually it's not good idea
raiseError(mask, SCL_object_table, name, "");
}
if (!REL.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, NULL, mask, SCL_object_table, false, name);
}
void SCL_check_view(thread_db* tdbb, const dsc* dsc_name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ v i e w
*
**************************************
*
* Functional description
* Given a view name, check for a set of privileges. The
* relation in question may or may not have been created, let alone
* scanned. This is used exclusively for meta-data operations.
*
**************************************/
SET_TDBB(tdbb);
// Get the name in CSTRING format, ending on NULL or SPACE
fb_assert(dsc_name->dsc_dtype == dtype_text);
const Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
dsc_name->dsc_length);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_v_security_v, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ name.c_str()
{
if (!REL.RDB$SECURITY_CLASS.NULL)
{
s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
}
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, NULL, mask, SCL_object_view, false, name);
}
void SCL_check_role(thread_db* tdbb, const Firebird::MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
*
* S C L _ c h e c k _ r o l e
*
**************************************
*
* Functional description
* Given a role name, check for a set of privileges.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
const SecurityClass* s_class = NULL;
AutoCacheRequest request(tdbb, irq_v_security_o, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) R IN RDB$ROLES
WITH R.RDB$ROLE_NAME EQ name.c_str()
{
if (!R.RDB$SECURITY_CLASS.NULL)
{
s_class = SCL_get_class(tdbb, R.RDB$SECURITY_CLASS);
}
}
END_FOR
SCL_check_access(tdbb, s_class, 0, 0, NULL, mask, SCL_object_role, false, name);
}
SecurityClass* SCL_get_class(thread_db* tdbb, const TEXT* par_string)
{
/**************************************
*
* S C L _ g e t _ c l a s s
*
**************************************
*
* Functional description
* Look up security class first in memory, then in database. If
* we don't find it, just return NULL. If we do, return a security
* class block.
*
**************************************/
SET_TDBB(tdbb);
// Name may be absent or terminated with NULL or blank. Clean up name.
if (!par_string)
return NULL;
Firebird::MetaName string(par_string);
if (string.isEmpty())
return NULL;
Jrd::Attachment* const attachment = tdbb->getAttachment();
// Look for the class already known
SecurityClassList* list = attachment->att_security_classes;
if (list && list->locate(string))
return list->current();
// Class isn't known. So make up a new security class block.
MemoryPool& pool = *attachment->att_pool;
SecurityClass* const s_class = FB_NEW_POOL(pool) SecurityClass(pool, string);
s_class->scl_flags = compute_access(tdbb, s_class, NULL, 0, NULL);
if (s_class->scl_flags & SCL_exists)
{
if (!list) {
attachment->att_security_classes = list = FB_NEW_POOL(pool) SecurityClassList(pool);
}
list->add(s_class);
return s_class;
}
delete s_class;
return NULL;
}
SecurityClass::flags_t SCL_get_mask(thread_db* tdbb, const TEXT* relation_name, const TEXT* field_name)
{
/**************************************
*
* S C L _ g e t _ m a s k
*
**************************************
*
* Functional description
* Get a protection mask for a named object. If field and
* relation names are present, get access to field. If just
* relation name, get access to relation.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
SecurityClass::flags_t access = ~0;
// If there's a relation, track it down
jrd_rel* relation;
if (relation_name && (relation = MET_lookup_relation(tdbb, relation_name)))
{
MET_scan_relation(tdbb, relation);
const SecurityClass* s_class;
if ( (s_class = SCL_get_class(tdbb, relation->rel_security_name.c_str())) )
{
access &= s_class->scl_flags;
}
const jrd_fld* field;
SSHORT id;
if (field_name &&
(id = MET_lookup_field(tdbb, relation, field_name)) >= 0 &&
(field = MET_get_field(relation, id)) &&
(s_class = SCL_get_class(tdbb, field->fld_security_name.c_str())))
{
access &= s_class->scl_flags;
}
}
return access & (SCL_select | SCL_drop | SCL_control |
SCL_insert | SCL_update |
SCL_delete | SCL_alter | SCL_references |
SCL_execute | SCL_usage);
}
bool SCL_role_granted(thread_db* tdbb, const UserId& usr, const TEXT* sql_role)
{
/**************************************
*
* S C L _ r o l e _ g r a n t e d
*
**************************************
*
* Functional description
* Check is sql_role granted to the user.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
if (!strcmp(sql_role, NULL_ROLE))
{
return true;
}
bool found = false;
AutoCacheRequest request(tdbb, irq_verify_role_name, IRQ_REQUESTS);
// CVC: The caller has hopefully uppercased the role or stripped quotes. Of course,
// uppercase should only happen if the role wasn't enclosed in quotes.
// Shortsighted developers named the field rdb$relation_name instead of rdb$object_name.
// This request is not exactly the same than irq_get_role_mem, sorry, I can't reuse that.
// If you think that an unknown role cannot be granted, think again: someone made sure
// in DYN that SYSDBA can do almost anything, including invalid grants.
FOR (REQUEST_HANDLE request) FIRST 1 RR IN RDB$ROLES
CROSS UU IN RDB$USER_PRIVILEGES
WITH RR.RDB$ROLE_NAME EQ sql_role
AND RR.RDB$ROLE_NAME EQ UU.RDB$RELATION_NAME
AND UU.RDB$OBJECT_TYPE EQ obj_sql_role
AND (UU.RDB$USER EQ usr.getUserName().c_str()
OR UU.RDB$USER EQ "PUBLIC")
AND UU.RDB$USER_TYPE EQ obj_user
AND UU.RDB$PRIVILEGE EQ "M"
{
if (!UU.RDB$USER.NULL)
found = true;
}
END_FOR
return found;
}
void UserId::findGrantedRoles(thread_db* tdbb) const
{
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
PreparedStatement::Builder sql;
MetaName usr_get_role;
string usr_get_priv;
sql << "with recursive role_tree as ( "
<< " select rdb$relation_name as nm, 0 as ur from rdb$user_privileges "
<< " where rdb$privilege = 'M' and rdb$field_name = 'D' and rdb$user = " << usr_user_name << " and rdb$user_type = 8 "
<< " union all "
<< " select rdb$role_name as nm, 1 as ur from rdb$roles "
<< " where rdb$role_name = " << usr_sql_role_name
<< " union all "
<< " select p.rdb$relation_name as nm, t.ur from rdb$user_privileges p "
<< " join role_tree t on t.nm = p.rdb$user "
<< " where p.rdb$privilege = 'M' and (p.rdb$field_name = 'D' or t.ur = 1)) "
<< "select " << sql("r.rdb$role_name, ", usr_get_role)
<< sql("r.rdb$system_privileges ", usr_get_priv)
<< " from role_tree t join rdb$roles r on t.nm = r.rdb$role_name ";
AutoPreparedStatement stmt(attachment->prepareStatement(tdbb, attachment->getSysTransaction(), sql));
AutoResultSet rs(stmt->executeQuery(tdbb, attachment->getSysTransaction()));
usr_granted_roles.clear();
usr_privileges.clearAll();
while (rs->fetch(tdbb))
{
if (!usr_granted_roles.exist(usr_get_role)) // SQL request can return duplicates
{
usr_granted_roles.add(usr_get_role);
Privileges p;
p.load(usr_get_priv.c_str());
usr_privileges |= p;
}
}
usr_flags &= ~USR_newrole;
}
void UserId::setRoleTrusted()
{
if (!usr_trusted_role.hasData())
Arg::Gds(isc_miss_trusted_role).raise();
setSqlRole(usr_trusted_role);
}
void UserId::sclInit(thread_db* tdbb, bool create, const UserId& tempId)
{
/**************************************
*
* S C L _ i n i t
*
**************************************
*
* Functional description
* Check database access control list.
*
* Finally fills UserId information
* (role, flags, etc.).
*
**************************************/
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
Jrd::Attachment* const attachment = tdbb->getAttachment();
const TEXT* sql_role = tempId.getSqlRole().nullStr();
// CVC: We'll verify the role and wipe it out when it doesn't exist
if (tempId.getUserName().hasData() && !create)
{
const TEXT* login_name = tempId.getUserName().c_str();
AutoCacheRequest request(tdbb, irq_get_role_name, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) X IN RDB$ROLES
WITH X.RDB$ROLE_NAME EQ login_name
{
ERR_post(Arg::Gds(isc_login_same_as_role_name) << Arg::Str(login_name));
}
END_FOR
}
// CVC: If we aren't creating a db and sql_role was specified,
// then verify it against rdb$roles and rdb$user_privileges
if (!create && sql_role && *sql_role)
{
if (!SCL_role_granted(tdbb, tempId, sql_role))
sql_role = NULL;
}
if (!sql_role)
sql_role = tempId.getTrustedRole().nullStr();
MetaName role_name(sql_role ? sql_role : NULL_ROLE);
MemoryPool& pool = *attachment->att_pool;
UserId* const user = FB_NEW_POOL(pool) UserId(pool, tempId);
user->setSqlRole(role_name);
attachment->att_user = user;
if (!create)
{
AutoCacheRequest request(tdbb, irq_get_att_class, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) X IN RDB$DATABASE
{
if (!X.RDB$SECURITY_CLASS.NULL)
attachment->att_security_class = SCL_get_class(tdbb, X.RDB$SECURITY_CLASS);
}
END_FOR
if (dbb->dbb_owner.isEmpty())
{
AutoRequest request2;
FOR(REQUEST_HANDLE request2)
FIRST 1 REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ "RDB$DATABASE"
{
if (!REL.RDB$OWNER_NAME.NULL)
dbb->dbb_owner = REL.RDB$OWNER_NAME;
}
END_FOR
}
}
else
{
dbb->dbb_owner = user->getUserName();
user->usr_privileges.load(INI_owner_privileges().c_str());
user->usr_granted_roles.clear();
user->usr_granted_roles.add("RDB$ADMIN");
user->usr_flags &= ~USR_newrole;
}
}
bool SCL_move_priv(SecurityClass::flags_t mask, Acl& acl)
{
/**************************************
*
* S C L _ m o v e _ p r i v
*
**************************************
*
* Functional description
* Given a mask of privileges, move privileges types to acl.
*
**************************************/
// Terminate identification criteria, and move privileges
acl.push(ACL_end);
acl.push(ACL_priv_list);
bool rc = false;
for (const P_NAMES* priv = p_names; priv->p_names_priv; priv++)
{
if (mask & priv->p_names_priv)
{
fb_assert(priv->p_names_acl <= MAX_UCHAR);
acl.push(priv->p_names_acl);
rc = true;
}
}
acl.push(0);
return rc;
}
SecurityClass* SCL_recompute_class(thread_db* tdbb, const TEXT* string)
{
/**************************************
*
* S C L _ r e c o m p u t e _ c l a s s
*
**************************************
*
* Functional description
* Something changed with a security class, recompute it. If we
* can't find it, return NULL.
*
**************************************/
SET_TDBB(tdbb);
SecurityClass* s_class = SCL_get_class(tdbb, string);
if (!s_class)
return NULL;
s_class->scl_flags = compute_access(tdbb, s_class, NULL, 0, NULL);
if (s_class->scl_flags & SCL_exists)
return s_class;
// Class no long exists - get rid of it!
const Firebird::MetaName m_string(string);
SecurityClassList* list = tdbb->getAttachment()->att_security_classes;
if (list && list->locate(m_string))
{
list->fastRemove();
delete s_class;
}
return NULL;
}
void SCL_release_all(SecurityClassList*& list)
{
/**************************************
*
* S C L _ r e l e a s e _ a l l
*
**************************************
*
* Functional description
* Release all security classes.
*
**************************************/
if (!list)
return;
if (list->getFirst())
{
do {
delete list->current();
} while (list->getNext());
}
delete list;
list = NULL;
}
SecurityClass::flags_t SCL_get_object_mask(const int object_type)
{
/**************************************
*
* S C L _ g e t _ o b j e c t _ m a s k
*
**************************************
*
* Functional description
* Get a protection mask for database object.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->getDatabase();
const TEXT* object_name = get_object_name(object_type);
if (!*object_name)
return 0;
const Jrd::SecurityClass* s_class = SCL_recompute_class(tdbb, object_name);
if (s_class)
return s_class->scl_flags;
return -1 & ~SCL_corrupt;
}
ULONG SCL_get_number(const UCHAR* acl)
{
/**************************************
*
* g e t _ n u m b e r
*
**************************************
*
* Functional description
* Get value of acl numeric string.
*
**************************************/
ULONG n = 0;
USHORT l = *acl++;
if (l)
{
do {
n = n * UIC_BASE + *acl++ - '0';
} while (--l);
}
return n;
}
static bool check_number(const UCHAR* acl, USHORT number)
{
/**************************************
*
* c h e c k _ n u m b e r
*
**************************************
*
* Functional description
* Check a string against and acl numeric string. If they don't match,
* return true.
*
**************************************/
return (SCL_get_number(acl) != number);
}
static bool check_user_group(thread_db* tdbb, const UCHAR* acl, USHORT number)
{
/**************************************
*
* c h e c k _ u s e r _ g r o u p
*
**************************************
*
* Functional description
*
* Check a string against an acl numeric string.
*
* logic:
*
* If the string contains user group name,
* then
* converts user group name to numeric user group id.
* else
* converts character user group id to numeric user group id.
*
* Check numeric user group id against an acl numeric string.
* If they don't match, return true.
*
**************************************/
SET_TDBB(tdbb);
ULONG n = 0;
USHORT l = *acl++;
if (l)
{
if (isdigit(*acl)) // this is a group id
{
do {
n = n * UIC_BASE + *acl++ - '0';
} while (--l);
}
else // processing group name
{
Firebird::string user_group_name;
do {
const TEXT one_char = *acl++;
user_group_name += LOWWER(one_char);
} while (--l);
// convert unix group name to unix group id
n = os_utils::get_user_group_id(user_group_name.c_str());
}
}
return (n != number);
}
static bool check_string(const UCHAR* acl, const Firebird::MetaName& string)
{
/**************************************
*
* c h e c k _ s t r i n g
*
**************************************
*
* Functional description
* Check a string against and acl string. If they don't match,
* return true.
*
**************************************/
fb_assert(acl);
const FB_SIZE_T length = *acl++;
const TEXT* const ptr = (TEXT*) acl;
return (string.compare(ptr, length) != 0);
}
static void get_string(const UCHAR* acl, Firebird::MetaName& string)
{
/**************************************
*
* g e t _ s t r i n g
*
**************************************
*
* Functional description
* Get a string from acl string.
*
**************************************/
fb_assert(acl);
const size_t length = *acl++;
const TEXT* const ptr = (TEXT*) acl;
string.assign(ptr, length);
}
static SecurityClass::flags_t compute_access(thread_db* tdbb,
const SecurityClass* s_class,
const jrd_rel* view,
SLONG obj_type,
const Firebird::MetaName& obj_name)
{
/**************************************
*
* c o m p u t e _ a c c e s s
*
**************************************
*
* Functional description
* Compute access for security class. If a relation block is
* present, it is a view, and we should check for enhanced view
* access permissions. Return a flag word of recognized privileges.
*
**************************************/
Acl acl;
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
jrd_tra* sysTransaction = attachment->getSysTransaction();
SecurityClass::flags_t privileges = 0;
SecurityClass::flags_t sysPriv = SCL_exists;
const SecurityClass::flags_t selectAnyObject = SCL_select | SCL_references;
const SecurityClass::flags_t accessAnyObject = SCL_insert | SCL_update | SCL_delete |
SCL_execute | SCL_usage | selectAnyObject;
const SecurityClass::flags_t anyDdl = SCL_create | SCL_alter | SCL_control | SCL_drop;
if (attachment->locksmith(tdbb, ACCESS_ANY_OBJECT_IN_DATABASE))
sysPriv |= accessAnyObject;
else if (attachment->locksmith(tdbb, SELECT_ANY_OBJECT_IN_DATABASE))
sysPriv |= selectAnyObject;
if (attachment->locksmith(tdbb, MODIFY_ANY_OBJECT_IN_DATABASE))
sysPriv |= anyDdl;
AutoCacheRequest request(tdbb, irq_l_security, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) X IN RDB$SECURITY_CLASSES
WITH X.RDB$SECURITY_CLASS EQ s_class->scl_name.c_str()
{
privileges |= sysPriv;
blb* blob = blb::open(tdbb, sysTransaction, &X.RDB$ACL);
UCHAR* buffer = acl.getBuffer(ACL_BLOB_BUFFER_SIZE);
UCHAR* end = buffer;
while (true)
{
end += blob->BLB_get_segment(tdbb, end, (USHORT) (acl.getCount() - (end - buffer)) );
if (blob->blb_flags & BLB_eof)
break;
// There was not enough space, realloc point acl to the correct location
if (blob->getFragmentSize())
{
const ptrdiff_t old_offset = end - buffer;
buffer = acl.getBuffer(acl.getCount() + ACL_BLOB_BUFFER_SIZE);
end = buffer + old_offset;
}
}
blob->BLB_close(tdbb);
blob = NULL;
acl.shrink(end - buffer);
if (acl.getCount() > 0)
privileges |= walk_acl(tdbb, acl, view, obj_type, obj_name);
}
END_FOR
return privileges;
}
static SecurityClass::flags_t walk_acl(thread_db* tdbb,
const Acl& acl,
const jrd_rel* view,
SLONG obj_type,
const Firebird::MetaName& obj_name)
{
/**************************************
*
* w a l k _ a c l
*
**************************************
*
* Functional description
* Walk an access control list looking for a hit. If a hit
* is found, return privileges.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
// Munch ACL. If we find a hit, eat up privileges.
UserId user = *attachment->att_user;
if (view && (view->rel_flags & REL_sql_relation))
{
// Use the owner of the view to perform the sql security
// checks with: (1) The view user must have sufficient privileges
// to the view, and (2a) the view owner must have sufficient
// privileges to the base table or (2b) the view must have
// sufficient privileges on the base table.
user.setUserName(view->rel_owner_name.c_str());
}
SecurityClass::flags_t privilege = 0;
const UCHAR* a = acl.begin();
if (*a++ != ACL_version)
{
BUGCHECK(160); // msg 160 wrong ACL version
}
const TEXT* p;
bool hit = false;
UCHAR c;
while ( (c = *a++) )
{
switch (c)
{
case ACL_id_list:
hit = true;
while ( (c = *a++) )
{
switch (c)
{
case id_person:
if (!(p = user.getUserName().nullStr()) || check_string(a, p))
hit = false;
break;
case id_project:
if (!(p = user.usr_project_name.nullStr()) || check_string(a, p))
hit = false;
break;
case id_organization:
if (!(p = user.usr_org_name.nullStr()) || check_string(a, p))
hit = false;
break;
case id_group:
if (check_user_group(tdbb, a, user.usr_group_id))
hit = false;
break;
case id_sql_role:
{
Firebird::MetaName role_name;
get_string(a, role_name);
if (!user.roleInUse(tdbb, role_name))
hit = false;
break;
}
case id_view:
if (!view || check_string(a, view->rel_name))
hit = false;
break;
case id_package:
case id_procedure:
case id_trigger:
case id_function:
if (c != obj_type || check_string(a, obj_name))
hit = false;
break;
case id_views:
// Disable this catch-all that messes up the view security.
// Note that this id_views is not generated anymore, this code
// is only here for compatibility. id_views was only
// generated for SQL.
hit = false;
if (!view)
hit = false;
break;
case id_user:
if (check_number(a, user.usr_user_id))
hit = false;
break;
case id_privilege:
if (!user.locksmith(tdbb, SCL_get_number(a)))
hit = false;
break;
case id_node:
break;
default:
return SCL_corrupt;
}
a += *a + 1;
}
break;
case ACL_priv_list:
if (hit)
{
while ( (c = *a++) )
{
switch (c)
{
case priv_control:
privilege |= SCL_control;
break;
case priv_select:
// Note that SELECT access must imply REFERENCES
// access for upward compatibility of existing
// security classes
privilege |= SCL_select | SCL_references;
break;
case priv_insert:
privilege |= SCL_insert;
break;
case priv_delete:
privilege |= SCL_delete;
break;
case priv_references:
privilege |= SCL_references;
break;
case priv_update:
privilege |= SCL_update;
break;
case priv_drop:
privilege |= SCL_drop;
break;
case priv_alter:
privilege |= SCL_alter;
break;
case priv_execute:
privilege |= SCL_execute;
break;
case priv_usage:
privilege |= SCL_usage;
break;
case priv_write:
// unused, but supported for backward compatibility
privilege |= SCL_insert | SCL_update | SCL_delete;
break;
case priv_grant:
// unused
break;
case priv_create:
privilege |= SCL_create;
break;
default:
return SCL_corrupt;
}
}
// For a relation the first hit does not give the privilege.
// Because, there could be some permissions for the table
// (for user1) and some permissions for a column on that
// table for public/user2, causing two hits.
// Hence, we do not return at this point.
// -- Madhukar Thakur (May 1, 1995)
}
else
while (*a++);
break;
default:
return SCL_corrupt;
}
}
fb_assert(a == acl.end());
return privilege;
}
void UserId::populateDpb(Firebird::ClumpletWriter& dpb)
{
if (usr_auth_block.hasData())
dpb.insertBytes(isc_dpb_auth_block, usr_auth_block.begin(), usr_auth_block.getCount());
else
dpb.insertString(isc_dpb_user_name, usr_user_name);
}
void UserId::makeRoleName(Firebird::MetaName& role, const int dialect)
{
if (role.isEmpty())
return;
switch (dialect)
{
case SQL_DIALECT_V5:
// Invoke utility twice: first to strip quotes, next to uppercase if needed
// For unquoted string nothing bad happens
fb_utils::dpbItemUpper(role);
fb_utils::dpbItemUpper(role);
break;
case SQL_DIALECT_V6_TRANSITION:
case SQL_DIALECT_V6:
fb_utils::dpbItemUpper(role);
break;
default:
break;
}
}
// get privilege bit by name
USHORT SCL_convert_privilege(thread_db* tdbb, jrd_tra* transaction, const Firebird::string& priv)
{
static GlobalPtr<Mutex> privCacheMutex;
static bool cacheFlag = false;
typedef NonPooled<MetaName, USHORT> CachedPriv;
static GlobalPtr<GenericMap<CachedPriv> > privCache;
if (!cacheFlag)
{
MutexLockGuard g(privCacheMutex, FB_FUNCTION);
if (!cacheFlag)
{
privCache->clear();
AutoCacheRequest request(tdbb, irq_get_priv_bit, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
T IN RDB$TYPES
WITH T.RDB$FIELD_NAME EQ 'RDB$SYSTEM_PRIVILEGES'
{
privCache->put(T.RDB$TYPE_NAME, T.RDB$TYPE);
}
END_FOR
cacheFlag = true;
}
}
USHORT rc;
if (!privCache->get(priv, rc))
(Arg::Gds(isc_wrong_prvlg) << priv).raise();
return rc;
}