8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-31 20:43:03 +01:00
firebird-mirror/src/jrd/scl.epp
dimitr 0a9e8c8382 Implemented CORE-1751. The changes include:
1) Make memory counters aggregated.
2) Add attachment pool and allocate appropriate resources out of this pool.
3) Always release attachments explicitly (via destructor).
4) Always delete user requests prior to attachment deletion.
5) Introduce memory usage counters per every monitoring object.
6) Misc refactoring.
Some pieces are still incomplete (although everything basically works), but I'd like to get feedback and testing sooner rather than later.
2008-05-06 08:46:39 +00:00

1344 lines
34 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.
*
*/
// This MUST be at the top of the file
#ifdef DARWIN
#define _STLP_CCTYPE
#endif
#include "firebird.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "../jrd/common.h"
#include "../jrd/ibase.h"
#include "../jrd/jrd.h"
#include "../jrd/ods.h"
#include "../jrd/scl.h"
#include "../jrd/jrd_pwd.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 "../jrd/gdsassert.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/enc_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/isc_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/grant_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/constants.h"
#include "../include/fb_exception.h"
#include "../common/utils_proto.h"
#include "../common/classes/array.h"
#include "../common/config/config.h"
const int UIC_BASE = 10;
const SLONG BLOB_BUFFER_SIZE = 4096; /* used to read in acl blob */
using namespace Jrd;
DATABASE DB = FILENAME "ODS.RDB";
static bool check_hex(const UCHAR*, USHORT);
static bool check_number(const UCHAR*, USHORT);
static bool check_user_group(const UCHAR*, USHORT, const ULONG);
static bool check_string(const UCHAR*, const Firebird::MetaName&);
static SecurityClass::flags_t compute_access(thread_db*, const SecurityClass*,
const jrd_rel*, const Firebird::MetaName&, const Firebird::MetaName&);
static SecurityClass::flags_t walk_acl(thread_db*, const UCHAR*, const jrd_rel*,
const Firebird::MetaName&, const Firebird::MetaName&, const ULONG);
static inline void check_and_move(UCHAR*& to, UCHAR from, Firebird::UCharBuffer& start, ULONG* length_ptr)
{
if ((start.begin() + *length_ptr) < (to + 1))
{
GRANT_realloc_acl(start, &to, length_ptr);
}
*to++ = from;
}
struct P_NAMES {
SecurityClass::flags_t p_names_priv;
USHORT p_names_acl;
const TEXT* p_names_string;
};
static const P_NAMES p_names[] =
{
{ SCL_protect, priv_protect, "protect" },
{ SCL_control, priv_control, "control" },
{ SCL_delete, priv_delete, "delete" },
{ SCL_sql_insert, priv_sql_insert, "insert/write" },
{ SCL_sql_update, priv_sql_update, "update/write" },
{ SCL_sql_delete, priv_sql_delete, "delete/write" },
{ SCL_write, priv_write, "write" },
{ SCL_read, priv_read, "read/select" },
{ SCL_grant, priv_grant, "grant" },
{ SCL_sql_references, priv_sql_references, "references" },
{ SCL_execute, priv_execute, "execute" },
{ 0, 0, "" }
};
void SCL_check_access(const SecurityClass* s_class,
SLONG view_id,
const Firebird::MetaName& trg_name,
const Firebird::MetaName& prc_name,
SecurityClass::flags_t mask,
const TEXT* type,
const char* name)
{
/**************************************
*
* S C L _ c h e c k _ a c c e s s
*
**************************************
*
* Functional description
* Check security class for desired permission. Check first that
* the desired access has been granted to the database then to the
* object in question.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
if (s_class && (s_class->scl_flags & SCL_corrupt))
{
ERR_post(isc_no_priv, isc_arg_string, "(ACL unrecognized)",
isc_arg_string, "security_class",
isc_arg_string, ERR_cstring(s_class->scl_name),
0);
}
const Attachment& attachment = *tdbb->getAttachment();
// Allow the database owner to back up a database even if he does not have
// read access to all the tables in the database
if ((attachment.att_flags & ATT_gbak_attachment) && (mask & SCL_read))
{
return;
}
// Allow the locksmith any access to database
if (attachment.locksmith())
{
return;
}
bool denied_db = false;
const SecurityClass* const att_class = attachment.att_security_class;
if (att_class && !(att_class->scl_flags & mask))
{
denied_db = true;
}
else
{
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 || trg_name.length() || prc_name.length()) &&
(compute_access(tdbb, s_class, view, trg_name, prc_name) & mask))
{
return;
}
}
const P_NAMES* names;
for (names = p_names; names->p_names_priv; names++)
{
if (names->p_names_priv & mask)
{
break;
}
}
if (denied_db) {
ERR_post(isc_no_priv,
isc_arg_string, names->p_names_string,
isc_arg_string, "DATABASE",
isc_arg_string, "",
0);
}
else {
ERR_post(isc_no_priv,
isc_arg_string, names->p_names_string,
isc_arg_string, type,
isc_arg_string, ERR_cstring(name),
0);
}
}
void SCL_check_access(const SecurityClass* s_class,
SLONG view_id,
const Firebird::MetaName& trg_name,
const Firebird::MetaName& prc_name,
SecurityClass::flags_t mask,
const TEXT* type,
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.
* Alternate entrypoint.
*
**************************************/
Firebird::string fullFieldName(name.c_str());
if (r_name.hasData())
{
fullFieldName = r_name.c_str();
fullFieldName += '.';
fullFieldName += name.c_str();
}
SCL_check_access(s_class, view_id, trg_name, prc_name,
mask, type, fullFieldName.c_str());
}
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);
Database* dbb = tdbb->getDatabase();
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;
jrd_req* request = NULL;
// 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);
END_FOR;
CMP_release(tdbb, request);
}
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);
END_FOR;
CMP_release (tdbb, request);
}
// Check if the relation exists. It may not have been created yet.
// Just return in that case.
if (reln_name.length() == 0) {
return;
}
SCL_check_access(s_class, 0, NULL, NULL, mask, object_table, reln_name);
request = NULL;
// Set up the exception mechanism, so that we can release the request
// in case of error in SCL_check_access
try {
// 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()
Firebird::string fullFieldName(reln_name.c_str());
fullFieldName += '.';
fullFieldName += RF.RDB$FIELD_NAME;
fullFieldName.rtrim();
if (!RF.RDB$SECURITY_CLASS.NULL) {
s_class = SCL_get_class(tdbb, RF.RDB$SECURITY_CLASS);
SCL_check_access(s_class, 0, NULL, NULL, mask,
object_column, fullFieldName);
}
else {
SCL_check_access(default_s_class, 0, NULL, NULL, mask,
object_column, fullFieldName);
}
END_FOR;
CMP_release(tdbb, request);
}
catch (const Firebird::Exception&) {
if (request) {
CMP_release(tdbb, request);
}
throw;
}
}
void SCL_check_procedure(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.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
// 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);
Database* dbb = tdbb->getDatabase();
const SecurityClass* s_class = NULL;
jrd_req* request = CMP_find_request(tdbb, irq_p_security, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) SPROC IN RDB$PROCEDURES
WITH SPROC.RDB$PROCEDURE_NAME EQ name.c_str()
if (!REQUEST(irq_p_security))
REQUEST(irq_p_security) = request;
if (!SPROC.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, SPROC.RDB$SECURITY_CLASS);
END_FOR;
if (!REQUEST(irq_p_security))
REQUEST(irq_p_security) = request;
SCL_check_access(s_class, 0, NULL, name, mask, object_procedure, name);
}
void SCL_check_relation(const dsc* dsc_name, SecurityClass::flags_t mask)
{
/**************************************
*
* 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.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
// 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);
Database* dbb = tdbb->getDatabase();
const SecurityClass* s_class = NULL;
jrd_req* request = CMP_find_request(tdbb, irq_v_security, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ name.c_str()
if (!REQUEST(irq_v_security))
REQUEST(irq_v_security) = request;
if (!REL.RDB$SECURITY_CLASS.NULL)
s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
END_FOR;
if (!REQUEST(irq_v_security))
REQUEST(irq_v_security) = request;
SCL_check_access(s_class, 0, NULL, NULL, mask, object_table, name);
}
SecurityClass* SCL_get_class(thread_db* tdbb, const TEXT* _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);
Database* dbb = tdbb->getDatabase();
// Name may be absent or terminated with NULL or blank. Clean up name.
if (!_string) {
return NULL;
}
Firebird::MetaName string(_string);
//fb_utils::exact_name(string);
if (string.isEmpty()) {
return NULL;
}
Attachment* 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) SecurityClass(pool, string);
s_class->scl_flags = compute_access(tdbb, s_class, NULL, NULL, NULL);
if (s_class->scl_flags & SCL_exists)
{
if (!list) {
attachment->att_security_classes = list = FB_NEW(pool) SecurityClassList(pool);
}
list->add(s_class);
return s_class;
}
delete s_class;
return NULL;
}
SecurityClass::flags_t SCL_get_mask(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. If neither, get
* access for database.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Attachment* attachment = tdbb->getAttachment();
// Start with database security class
const SecurityClass* s_class = attachment->att_security_class;
SecurityClass::flags_t access = (s_class) ? s_class->scl_flags : -1;
// 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);
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)) >= 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_read | SCL_write | SCL_delete | SCL_control |
SCL_grant | SCL_sql_insert | SCL_sql_update |
SCL_sql_delete | SCL_protect | SCL_sql_references |
SCL_execute);
}
void SCL_init(bool create,
const UserId& tempId,
thread_db* tdbb)
{
/**************************************
*
* S C L _ i n i t
*
**************************************
*
* Functional description
* Check database access control list.
*
* Checks the userinfo database to get the
* password and other stuff about the specified
* user. Compares the password to that passed
* in, encrypting if necessary.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
const USHORT major_version = dbb->dbb_ods_version;
const USHORT minor_original = dbb->dbb_minor_original;
const TEXT* sql_role = tempId.usr_sql_role_name.nullStr();
Firebird::string loginName(tempId.usr_user_name);
loginName.upper();
const TEXT* login_name = loginName.c_str();
Firebird::MetaName role_name;
// CVC: Let's clean any role in pre-ODS9 attachments
bool preODS9 = true;
/***************************************************************
**
** skip reading system relation RDB$ROLES when attaching pre ODS_9_0 database
**
****************************************************************/
// CVC: We'll verify the role and wipe it out when it doesn't exist
if (ENCODE_ODS(major_version, minor_original) >= ODS_9_0) {
preODS9 = false;
if (strlen(login_name) != 0)
{
if (!create)
{
jrd_req* request = CMP_find_request(tdbb, irq_get_role_name,
IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) X IN RDB$ROLES
WITH X.RDB$ROLE_NAME EQ login_name
if (!REQUEST(irq_get_role_name))
REQUEST(irq_get_role_name) = request;
EXE_unwind(tdbb, request);
ERR_post(isc_login_same_as_role_name,
isc_arg_string, ERR_cstring(login_name), 0);
END_FOR;
if (!REQUEST(irq_get_role_name))
REQUEST(irq_get_role_name) = request;
}
}
// CVC: If this is ODS>=ODS_9_0 and 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 && strcmp(sql_role, NULL_ROLE)) {
bool found = false;
jrd_req* request = CMP_find_request (tdbb, irq_verify_role_name, IRQ_REQUESTS);
// CVC: The caller has hopefully uppercased the role or stripped quotes. Of course,
// uppercase-UPPER7 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 login_name
OR UU.RDB$USER EQ "PUBLIC")
AND UU.RDB$USER_TYPE EQ obj_user
AND UU.RDB$PRIVILEGE EQ "M"
if (!REQUEST (irq_verify_role_name))
REQUEST (irq_verify_role_name) = request;
if (!UU.RDB$USER.NULL)
found = true;
END_FOR;
if (!REQUEST (irq_verify_role_name))
REQUEST (irq_verify_role_name) = request;
if (!found && (tempId.usr_flags & USR_trole))
{
jrd_req* request = CMP_find_request (tdbb, irq_verify_trusted_role, IRQ_REQUESTS);
FOR (REQUEST_HANDLE request) FIRST 1 RR IN RDB$ROLES
WITH RR.RDB$ROLE_NAME EQ sql_role
AND RR.RDB$SYSTEM_FLAG NOT MISSING
if (!REQUEST (irq_verify_trusted_role))
REQUEST (irq_verify_trusted_role) = request;
if (RR.RDB$SYSTEM_FLAG & ROLE_FLAG_MAY_TRUST)
found = true;
END_FOR;
if (!REQUEST (irq_verify_trusted_role))
REQUEST (irq_verify_trusted_role) = request;
}
if (!found)
{
role_name = NULL_ROLE;
}
}
}
if (sql_role) {
if ((!preODS9) && (role_name != NULL_ROLE)) {
role_name = sql_role;
}
}
else {
role_name = NULL_ROLE;
}
Attachment* const attachment = tdbb->getAttachment();
MemoryPool& pool = *attachment->att_pool;
UserId* const user = FB_NEW(pool) UserId(pool, tempId);
user->usr_sql_role_name = role_name.c_str();
attachment->att_user = user;
if (!create) {
jrd_req* handle = NULL;
jrd_req* handle1 = NULL;
jrd_req* handle2 = NULL;
FOR(REQUEST_HANDLE handle) X IN RDB$DATABASE
if (!X.RDB$SECURITY_CLASS.NULL)
attachment->att_security_class = SCL_get_class(tdbb, X.RDB$SECURITY_CLASS);
END_FOR;
CMP_release(tdbb, handle);
FOR(REQUEST_HANDLE handle1)
FIRST 1 REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ "RDB$DATABASE"
if (!REL.RDB$OWNER_NAME.NULL && user->usr_user_name.hasData())
{
char name[129];
*name = user->usr_user_name.length();
user->usr_user_name.copyTo(name + 1, sizeof(name) - 1);
if (!check_string(reinterpret_cast<const UCHAR*>(name),
REL.RDB$OWNER_NAME))
{
user->usr_flags |= USR_owner;
}
}
END_FOR;
CMP_release(tdbb, handle1);
FOR(REQUEST_HANDLE handle2) R IN RDB$ROLES
WITH R.RDB$ROLE_NAME EQ role_name.c_str()
if (!R.RDB$SYSTEM_FLAG.NULL)
{
if (R.RDB$SYSTEM_FLAG & ROLE_FLAG_DBO)
user->usr_flags |= USR_dba;
}
END_FOR;
CMP_release(tdbb, handle2);
}
else {
user->usr_flags |= USR_owner;
}
}
void SCL_move_priv(UCHAR** acl_ptr, SecurityClass::flags_t mask,
Firebird::UCharBuffer& start_ptr, ULONG* length_ptr)
{
/**************************************
*
* S C L _ m o v e _ p r i v
*
**************************************
*
* Functional description
* Given a mask of privileges, move privileges types to acl.
*
**************************************/
UCHAR* p = *acl_ptr;
// Terminate identification criteria, and move privileges
check_and_move(p, ACL_end, start_ptr, length_ptr);
check_and_move(p, ACL_priv_list, start_ptr, length_ptr);
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);
check_and_move(p, priv->p_names_acl, start_ptr, length_ptr);
}
}
check_and_move(p, 0, start_ptr, length_ptr);
*acl_ptr = p;
}
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, NULL, 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;
}
static bool check_hex(const UCHAR* acl, USHORT number)
{
/**************************************
*
* c h e c k _ h e x
*
**************************************
*
* Functional description
* Check a string against and acl numeric string. If they don't match,
* return true.
*
**************************************/
int n = 0;
USHORT l = *acl++;
if (l)
{
do {
const TEXT c = *acl++;
n *= 10;
if (c >= '0' && c <= '9') {
n += c - '0';
}
else if (c >= 'a' && c <= 'f') {
n += c - 'a' + 10;
}
else if (c >= 'A' && c <= 'F') {
n += c - 'A' + 10;
}
} while (--l);
}
return (n != number);
}
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.
*
**************************************/
int n = 0;
USHORT l = *acl++;
if (l)
{
do {
n = n * UIC_BASE + *acl++ - '0';
} while (--l);
}
return (n != number);
}
static bool check_user_group(const UCHAR* acl,
USHORT number,
const ULONG length)
{
/**************************************
*
* 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.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->getDatabase();
SLONG 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::UCharBuffer buffer;
fb_assert(l < length); // Not sure how we can guarantee this.
UCHAR* user_group_name = buffer.getBuffer(length);
do {
const TEXT one_char = *acl++;
*user_group_name++ = LOWWER(one_char);
} while (--l);
*user_group_name = '\0';
user_group_name = buffer.begin();
// convert unix group name to unix group id
n = ISC_get_user_group_id(reinterpret_cast<const TEXT*>(user_group_name));
}
}
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 size_t length = *acl++;
const TEXT* const ptr = (TEXT*) acl;
return (string.compare(ptr, length) != 0);
}
static SecurityClass::flags_t compute_access(thread_db* tdbb,
const SecurityClass* s_class,
const jrd_rel* view,
const Firebird::MetaName& trg_name,
const Firebird::MetaName& prc_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.
*
**************************************/
Firebird::UCharBuffer str_buffer;
UCHAR* buffer = str_buffer.getBuffer(BLOB_BUFFER_SIZE);
SLONG length = BLOB_BUFFER_SIZE;
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
SecurityClass::flags_t privileges = SCL_scanned;
jrd_req* request = CMP_find_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()
if (!REQUEST(irq_l_security))
REQUEST(irq_l_security) = request;
privileges |= SCL_exists;
blb* blob = BLB_open(tdbb, dbb->dbb_sys_trans, &X.RDB$ACL);
UCHAR* acl = buffer;
while (true)
{
acl += BLB_get_segment(tdbb, blob, acl,
(USHORT) (length -
((acl - buffer) *
(sizeof(buffer[0])))));
if (blob->blb_flags & BLB_eof)
break;
// There was not enough space, realloc point acl to the correct location
if (blob->blb_fragment_size)
{
const ULONG old_offset = (ULONG) (acl - buffer);
length += BLOB_BUFFER_SIZE;
buffer = str_buffer.getBuffer(length);
acl = buffer + old_offset;
}
}
BLB_close(tdbb, blob);
blob = NULL;
if (acl != buffer)
{
privileges |= walk_acl( tdbb,
buffer,
view,
trg_name,
prc_name,
length);
}
END_FOR;
if (!REQUEST(irq_l_security))
REQUEST(irq_l_security) = request;
return privileges;
}
static SecurityClass::flags_t walk_acl(thread_db* tdbb,
const UCHAR* acl,
const jrd_rel* view,
const Firebird::MetaName& trg_name,
const Firebird::MetaName& prc_name,
const ULONG length)
{
/**************************************
*
* 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);
Database* dbb = tdbb->getDatabase();
// Munch ACL. If we find a hit, eat up privileges.
UserId user = *tdbb->getAttachment()->att_user;
const TEXT* role_name = user.usr_sql_role_name.nullStr();
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.usr_user_name = view->rel_owner_name.c_str();
}
SecurityClass::flags_t privilege = 0;
if (*acl++ != ACL_version)
{
BUGCHECK(160); // msg 160 wrong ACL version
}
if (user.locksmith())
{
return -1 & ~SCL_corrupt;
}
const TEXT* p;
bool hit = false;
UCHAR c;
while ( (c = *acl++) )
{
switch (c)
{
case ACL_id_list:
hit = true;
while ( (c = *acl++) )
{
switch (c)
{
case id_person:
if (!(p = user.usr_user_name.nullStr()) || check_string(acl, p))
hit = false;
break;
case id_project:
if (!(p = user.usr_project_name.nullStr()) || check_string(acl, p))
hit = false;
break;
case id_organization:
if (!(p = user.usr_org_name.nullStr()) || check_string(acl, p))
hit = false;
break;
case id_group:
if (check_user_group(acl,
user.usr_group_id,
length))
{
hit = false;
}
break;
case id_sql_role:
if (!role_name || check_string(acl, role_name))
hit = false;
else
{
TEXT login_name[129];
TEXT* pln = login_name;
const TEXT* q = user.usr_user_name.c_str();
while (*pln++ = UPPER7(*q)) {
++q;
}
hit = false;
jrd_req* request =
CMP_find_request(tdbb, irq_get_role_mem,
IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) U IN RDB$USER_PRIVILEGES WITH
(U.RDB$USER EQ login_name OR
U.RDB$USER EQ "PUBLIC") AND
U.RDB$USER_TYPE EQ obj_user AND
U.RDB$RELATION_NAME EQ user.usr_sql_role_name.c_str() AND
U.RDB$OBJECT_TYPE EQ obj_sql_role AND
U.RDB$PRIVILEGE EQ "M"
if (!REQUEST(irq_get_role_mem))
REQUEST(irq_get_role_mem) = request;
if (!U.RDB$USER.NULL)
hit = true;
END_FOR;
if (!REQUEST(irq_get_role_mem))
REQUEST(irq_get_role_mem) = request;
}
break;
case id_view:
if (!view || check_string(acl, view->rel_name))
hit = false;
break;
case id_procedure:
if (check_string(acl, prc_name))
hit = false;
break;
case id_trigger:
if (check_string(acl, trg_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(acl, user.usr_user_id)) {
hit = false;
}
break;
case id_node:
if (check_hex(acl, user.usr_node_id)) {
hit = false;
}
break;
default:
return SCL_corrupt;
}
acl += *acl + 1;
}
break;
case ACL_priv_list:
if (hit) {
while ( (c = *acl++) )
switch (c) {
case priv_control:
privilege |= SCL_control;
break;
case priv_read:
// Note that READ access must imply REFERENCES
// access for upward compatibility of existing
// security classes
privilege |= SCL_read | SCL_sql_references;
break;
case priv_write:
privilege |=
SCL_write | SCL_sql_insert | SCL_sql_update |
SCL_sql_delete;
break;
case priv_sql_insert:
privilege |= SCL_sql_insert;
break;
case priv_sql_delete:
privilege |= SCL_sql_delete;
break;
case priv_sql_references:
privilege |= SCL_sql_references;
break;
case priv_sql_update:
privilege |= SCL_sql_update;
break;
case priv_delete:
privilege |= SCL_delete;
break;
case priv_grant:
privilege |= SCL_grant;
break;
case priv_protect:
privilege |= SCL_protect;
break;
case priv_execute:
privilege |= SCL_execute;
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 (*acl++);
break;
default:
return SCL_corrupt;
}
}
return privilege;
}