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