8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-30 19:23:03 +01:00
firebird-mirror/src/jrd/scl.epp
asfernandes adf1fd737d Misc.
2015-05-02 03:54:03 +00:00

1746 lines
45 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 "../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 "../common/IntlUtil.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. Check first that
* the desired access has been granted to the database then to the
* object in question.
*
**************************************/
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));
}
const Jrd::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.isGbak() && (mask & SCL_select))
return;
// Allow the locksmith any access to database
if (attachment.locksmith())
return;
// 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();
// Allow the locksmith any access to database
if (attachment->locksmith())
return;
const SecurityClass* const att_class = attachment->att_security_class;
if (att_class && (att_class->scl_flags & mask))
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)
{
/**************************************
*
* 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 (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);
//fb_utils::exact_name(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) 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) 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);
Jrd::Attachment* const 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 &&
(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;
}
Firebird::string loginName(usr.usr_user_name);
IntlUtil::toUpper(loginName);
const TEXT* login_name = loginName.c_str();
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 login_name
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;
}
bool SCL_admin_role(thread_db* tdbb, const TEXT* sql_role)
{
/**************************************
*
* S C L _ a d m i n _ r o l e
*
**************************************
*
* Functional description
* Check is sql_role is an admin role.
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
bool adminRole = false;
AutoCacheRequest request(tdbb, irq_is_admin_role, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request) R IN RDB$ROLES
WITH R.RDB$ROLE_NAME EQ sql_role
AND R.RDB$SYSTEM_FLAG != 0
{
adminRole = true;
}
END_FOR
return adminRole;
}
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.
*
* 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.usr_sql_role_name.nullStr();
// CVC: We'll verify the role and wipe it out when it doesn't exist
if (tempId.usr_user_name.hasData())
{
if (!create)
{
Firebird::string loginName(tempId.usr_user_name);
IntlUtil::toUpper(loginName);
const TEXT* login_name = loginName.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.usr_trusted_role.nullStr();
MetaName role_name(sql_role ? sql_role : NULL_ROLE);
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)
{
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
}
if (dbb->dbb_owner == user->usr_user_name)
user->usr_flags |= USR_owner;
if (sql_role && SCL_admin_role(tdbb, role_name.c_str()))
user->usr_flags |= USR_dba;
}
else
{
dbb->dbb_owner = user->usr_user_name;
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
acl.push(ACL_end);
acl.push(ACL_priv_list);
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);
}
}
acl.push(0);
}
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();
UserId* user = tdbb->getAttachment()->att_user;
/***
if (object_type == obj_roles)
{
if (user->isSecAdmin())
return -1 & ~SCL_corrupt;
}
***/
const TEXT* object_name = get_object_name(object_type);
const Jrd::SecurityClass* s_class = SCL_recompute_class(tdbb, object_name);
if (s_class)
return s_class->scl_flags;
return -1 & ~SCL_corrupt;
}
void SCL_set_user(const Firebird::MetaName& user_name, UserId& user)
{
/**************************************
*
* S C L _ s e t _ u s e r
*
**************************************
*
* Functional description
* Set user attributes.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->getDatabase();
user.usr_user_name = user_name.c_str();
if (!user.usr_user_name.compare(dbb->dbb_owner.c_str()))
user.usr_flags |= USR_owner;
}
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);
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 = 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 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;
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 |= SCL_exists;
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 = *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 if (user.usr_sql_role_name != user.usr_trusted_role)
{
Firebird::string loginName(user.usr_user_name);
IntlUtil::toUpper(loginName);
const TEXT* login_name = loginName.c_str();
bool roleHit = false;
AutoCacheRequest 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 (!U.RDB$USER.NULL)
roleHit = true;
}
END_FOR
if (!roleHit)
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_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 Jrd::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);
}