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

1314 lines
33 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;
using namespace Jrd;
using namespace Firebird;
DATABASE DB = FILENAME "ODS.RDB";
static bool check_hex(const UCHAR*, USHORT);
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*, const Firebird::MetaName&, const Firebird::MetaName&);
static SecurityClass::flags_t walk_acl(thread_db* tdbb, const Acl&, const jrd_rel*,
const Firebird::MetaName&, const Firebird::MetaName&);
static inline void check_and_move(UCHAR from, Acl& to)
{
to.push(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(thread_db* tdbb,
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.
*
**************************************/
SET_TDBB(tdbb);
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));
}
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(Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
Arg::Str("DATABASE") <<
Arg::Str(""));
}
else {
ERR_post(Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
Arg::Str(type) <<
Arg::Str(name));
}
}
void SCL_check_access(thread_db* tdbb,
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.
*
**************************************/
SET_TDBB(tdbb);
Firebird::string fullFieldName(name.c_str());
if (r_name.hasData())
{
fullFieldName = r_name.c_str();
fullFieldName += '.';
fullFieldName += name.c_str();
}
SCL_check_access(tdbb, 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(tdbb, 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(tdbb, s_class, 0, NULL, NULL, mask, object_column, fullFieldName);
}
else {
SCL_check_access(tdbb, 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(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);
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(tdbb, s_class, 0, NULL, name, mask, object_procedure, name);
}
void SCL_check_relation(thread_db* tdbb, 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.
*
**************************************/
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);
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(tdbb, 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(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. If neither, get
* access for database.
*
**************************************/
SET_TDBB(tdbb);
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(thread_db* tdbb, bool create, const UserId& tempId)
{
/**************************************
*
* 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(Arg::Gds(isc_login_same_as_role_name) << Arg::Str(login_name));
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(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
check_and_move(ACL_end, acl);
check_and_move(ACL_priv_list, acl);
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(priv->p_names_acl, acl);
}
}
check_and_move(0, acl);
}
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(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);
//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::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 = ISC_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 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.
*
**************************************/
Acl acl;
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* buffer = acl.getBuffer(ACL_BLOB_BUFFER_SIZE);
UCHAR* end = buffer;
while (true)
{
end += BLB_get_segment(tdbb, blob, 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->blb_fragment_size)
{
const ptrdiff_t old_offset = end - buffer;
buffer = acl.getBuffer(acl.getCount() + ACL_BLOB_BUFFER_SIZE);
end = buffer + old_offset;
}
}
BLB_close(tdbb, blob);
blob = NULL;
acl.shrink(end - buffer);
if (acl.getCount() > 0)
{
privileges |= walk_acl(tdbb, acl, view, trg_name, prc_name);
}
END_FOR;
if (!REQUEST(irq_l_security))
REQUEST(irq_l_security) = request;
return privileges;
}
static SecurityClass::flags_t walk_acl(thread_db* tdbb,
const Acl& acl,
const jrd_rel* view,
const Firebird::MetaName& trg_name,
const Firebird::MetaName& prc_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);
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;
const UCHAR* a = acl.begin();
if (*a++ != 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 = *a++) )
{
switch (c)
{
case ACL_id_list:
hit = true;
while ( (c = *a++) )
{
switch (c)
{
case id_person:
if (!(p = user.usr_user_name.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:
if (!role_name || check_string(a, 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(a, view->rel_name))
hit = false;
break;
case id_procedure:
if (check_string(a, prc_name))
hit = false;
break;
case id_trigger:
if (check_string(a, 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(a, user.usr_user_id)) {
hit = false;
}
break;
case id_node:
if (check_hex(a, user.usr_node_id)) {
hit = false;
}
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_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 (*a++);
break;
default:
return SCL_corrupt;
}
}
fb_assert(a == acl.end());
return privilege;
}