8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-25 02:03:03 +01:00
firebird-mirror/src/jrd/grant.epp
alexpeshkoff 5729790ed6 1. Applied class MetaName
2. Small generic cleanup
2005-05-12 18:28:04 +00:00

1290 lines
33 KiB
Plaintext

/*
* PROGRAM: JRD Access Method
* MODULE: grant.epp
* DESCRIPTION: SQL Grant/Revoke 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): ______________________________________.
*
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
*
*/
#include "firebird.h"
#include <stdio.h>
#include <string.h>
#include "../jrd/common.h"
#include "../jrd/ibase.h"
#include "../jrd/jrd.h"
#include "../jrd/scl.h"
#include "../jrd/acl.h"
#include "../jrd/irq.h"
#include "../jrd/blb.h"
#include "../jrd/btr.h"
#include "../jrd/req.h"
#include "../jrd/tra.h"
#include "../jrd/val.h"
#include "../jrd/met.h"
#include "../jrd/intl.h"
#include "../jrd/all_proto.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/dyn_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/grant_proto.h"
#include "../jrd/jrd_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/thd.h"
#include "../common/utils_proto.h"
using namespace Jrd;
/* privileges given to the owner of a relation */
const SecurityClass::flags_t OWNER_PRIVS = SCL_control | SCL_read | SCL_write | SCL_delete | SCL_protect;
const SecurityClass::flags_t VIEW_PRIVS = SCL_read | SCL_write | SCL_delete;
const ULONG ACL_BUFFER_SIZE = 4096;
static const char* DEFAULT_CLASS = "SQL$DEFAULT";
#define CHECK_ACL_BOUND(to, start, length_ptr, move_length)\
{\
if (((start).begin() + *length_ptr) < (to + move_length)) {\
GRANT_realloc_acl(start, &to, length_ptr);\
}\
}
#define CHECK_AND_MOVE(to, from, start, length_ptr) {CHECK_ACL_BOUND (to, start, length_ptr, 1); *(to)++ = from;}
#define CHECK_MOVE_INCR(to, from, start, length_ptr) {CHECK_ACL_BOUND (to, start, length_ptr, 1); *(to)++ = (from)++;}
DATABASE DB = STATIC "yachts.lnk";
static void define_default_class(thread_db*, const TEXT*, Firebird::MetaName&, const UCHAR*, USHORT);
#ifdef NOT_USED_OR_REPLACED
static void delete_security_class(thread_db*, const TEXT*);
#endif
static void finish_security_class(UCHAR**, SecurityClass::flags_t, UCharBuffer&, ULONG*);
static void get_object_info(thread_db*, const TEXT*, SSHORT,
Firebird::MetaName&, Firebird::MetaName&, Firebird::MetaName&, bool&);
static SecurityClass::flags_t get_public_privs(thread_db*, const TEXT*, SSHORT);
static void get_user_privs(thread_db*, UCHAR**, const TEXT*, SSHORT, const Firebird::MetaName&,
SecurityClass::flags_t, UCharBuffer&, ULONG*);
static void grant_user(UCHAR**, const Firebird::MetaName&, SSHORT, SecurityClass::flags_t,
UCharBuffer&, ULONG*);
#ifdef NOT_USED_OR_REPLACED
static void grant_views(TEXT**, SecurityClass::flags_t, UCharBuffer&, ULONG*);
static void purge_default_class(TEXT*, SSHORT);
#endif
static SecurityClass::flags_t save_field_privileges(thread_db*, UCharBuffer&, UCHAR**,
const TEXT*, const Firebird::MetaName&,
SecurityClass::flags_t, ULONG*);
static void save_security_class(thread_db*, const Firebird::MetaName&, const UCHAR*, USHORT);
static SecurityClass::flags_t trans_sql_priv(const TEXT*);
static SecurityClass::flags_t squeeze_acl(UCHAR*, UCHAR**, const Firebird::MetaName&, SSHORT);
static bool check_string(const UCHAR*, const Firebird::MetaName&);
void GRANT_realloc_acl(UCharBuffer& start_ptr,
UCHAR** write_ptr,
ULONG* buffer_length)
{
/**************************************
*
* G R A N T _ r e a l l o c _ a c l
*
**************************************
*
* Functional description
* The ACl list is greater than the current length, Increase it by
* MAX_AXL_SIZE. Do a ERR_punt in case of no memory!
*
**************************************/
const ULONG old_offset = *write_ptr - start_ptr.begin();
const ULONG realloc_length = *buffer_length + ACL_BUFFER_SIZE;
// realloc the new length, ERR_punt incase of no memory
// the write_ptr is set back to the same offset in the new buffer
*write_ptr = start_ptr.getBuffer(realloc_length) + old_offset;
*buffer_length = realloc_length;
}
bool GRANT_privileges( thread_db* tdbb, SSHORT phase, DeferredWork* work,
jrd_tra*) // unused param, makes dfw.epp happy
{
/**************************************
*
* G R A N T _ p r i v i l e g e s
*
**************************************
*
* Functional description
* Compute access control list from SQL privileges.
* This calculation is tricky and involves interaction between
* the relation-level and field-level privileges. Do not change
* the order of operations lightly.
*
**************************************/
switch (phase) {
case 1:
case 2:
return true;
case 3:
{
ULONG length = ACL_BUFFER_SIZE, *length_ptr = &length;
ULONG default_length = ACL_BUFFER_SIZE;
ULONG* default_length_ptr = &default_length;
SET_TDBB(tdbb);
bool restrct = false;
Database* dbb = tdbb->tdbb_database;
#ifdef V4_THREADING
V4_JRD_MUTEX_LOCK(dbb->dbb_mutexes + DBB_MUTX_grant_priv);
#endif
Firebird::MetaName s_class, owner, default_class;
bool view; // unused after being retrieved.
get_object_info(tdbb, work->dfw_name.c_str(), work->dfw_id, owner,
s_class, default_class, view);
if (s_class.length() == 0) {
#ifdef V4_THREADING
V4_JRD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_grant_priv);
#endif
return false;
}
/* start the acl off by giving the owner all privileges */
UCharBuffer str_buffer;
UCharBuffer str_default_buffer;
UCHAR* acl = str_buffer.getBuffer(ACL_BUFFER_SIZE);
str_default_buffer.getBuffer(ACL_BUFFER_SIZE);
CHECK_AND_MOVE(acl, ACL_version, str_buffer, length_ptr);
grant_user(&acl,
owner,
obj_user,
((work->dfw_id == obj_procedure) ?
(SCL_execute | OWNER_PRIVS) : OWNER_PRIVS),
str_buffer,
length_ptr);
/* Pick up any relation-level privileges */
const SecurityClass::flags_t public_priv =
get_public_privs(tdbb, work->dfw_name.c_str(), work->dfw_id);
get_user_privs(tdbb, &acl, work->dfw_name.c_str(), work->dfw_id,
owner, public_priv, str_buffer, length_ptr);
if (work->dfw_id == obj_relation)
{
/* If we have the space to copy the acl list
no need to realloc */
if (length > default_length) {
str_default_buffer.getBuffer(length);
default_length = length;
}
/* Now handle field-level privileges. This might require adding
UPDATE privilege to the relation-level acl, Therefore, save
off the relation acl because we need to add a default field
acl in that case. */
MOVE_FAST(str_buffer.begin(), str_default_buffer.begin(),
(int) (acl - str_buffer.begin()));
UCHAR* default_acl =
str_default_buffer.begin() + (acl - str_buffer.begin());
const UCHAR* const temp_acl = acl;
const SecurityClass::flags_t aggregate_public =
save_field_privileges(tdbb, str_buffer,
&acl, work->dfw_name.c_str(),
owner, public_priv,
length_ptr);
/* SQL tables don't need the 'all priviliges to all views' acl anymore.
This special acl was only generated for SQL. */
#ifdef NOT_USED_OR_REPLACED
/* grant_views (&acl, VIEW_PRIVS); */
#endif
/* finish off and store the security class for the relation */
finish_security_class(&acl, aggregate_public, str_buffer,
length_ptr);
save_security_class(tdbb,
s_class,
str_buffer.begin(),
(USHORT)(acl - str_buffer.begin()));
if (temp_acl != acl) /* relation privs were added? */
restrct = true;
/* if there have been privileges added at the relation level which
need to be restricted from other fields in the relation,
update the acl for them */
if (restrct)
{
finish_security_class(&default_acl, public_priv,
str_default_buffer,
default_length_ptr);
define_default_class(
tdbb,
work->dfw_name.c_str(),
default_class,
str_default_buffer.begin(),
(USHORT)(default_acl - str_default_buffer.begin()));
}
}
else {
finish_security_class(&acl, public_priv, str_buffer, length_ptr);
save_security_class(tdbb, s_class, str_buffer.begin(),
(USHORT)(acl - str_buffer.begin()));
}
#ifdef V4_THREADING
V4_JRD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_grant_priv);
#endif
break;
}
default:
break;
}
DFW_perform_system_work();
return false;
}
static void define_default_class(
thread_db* tdbb,
const TEXT* relation_name,
Firebird::MetaName& default_class,
const UCHAR* buffer, USHORT length)
{
/**************************************
*
* d e f i n e _ d e f a u l t _ c l a s s
*
**************************************
*
* Functional description
* Update the default security class for fields
* which have not been specifically granted
* any privileges. We must grant them all
* privileges which were specifically granted
* at the relation level, but none of the
* privileges we added at the relation level
* for the purpose of accessing other fields.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->tdbb_database;
if (default_class.length() == 0) {
default_class.printf("%s%" QUADFORMAT "d", DEFAULT_CLASS,
DPM_gen_id(tdbb, MET_lookup_generator(tdbb, DEFAULT_CLASS), false,
(SINT64) 1));
jrd_req* request = CMP_find_request(tdbb, irq_grant7, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ relation_name
if (!REQUEST(irq_grant7))
REQUEST(irq_grant7) = request;
MODIFY REL USING
REL.RDB$DEFAULT_CLASS.NULL = FALSE;
jrd_vtof(default_class.c_str(), REL.RDB$DEFAULT_CLASS,
sizeof(REL.RDB$DEFAULT_CLASS));
END_MODIFY;
END_FOR;
if (!REQUEST(irq_grant7))
REQUEST(irq_grant7) = request;
}
save_security_class(tdbb, default_class, buffer, length);
dsc desc;
desc.dsc_dtype = dtype_text;
desc.dsc_sub_type = 0;
desc.dsc_scale = 0;
desc.dsc_ttype() = ttype_metadata;
desc.dsc_address = (UCHAR *) relation_name;
desc.dsc_length = strlen(relation_name);
DFW_post_work(dbb->dbb_sys_trans, dfw_scan_relation, &desc, 0);
}
#ifdef NOT_USED_OR_REPLACED
static void delete_security_class( thread_db* tdbb, const TEXT* s_class)
{
/**************************************
*
* d e l e t e _ s e c u r i t y _ c l a s s
*
**************************************
*
* Functional description
* Delete a security class.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->tdbb_database;
jrd_req* handle = NULL;
FOR(REQUEST_HANDLE handle)
CLS IN RDB$SECURITY_CLASSES
WITH CLS.RDB$SECURITY_CLASS EQ s_class
ERASE CLS;
END_FOR;
CMP_release(tdbb, handle);
}
#endif // NOT_USED_OR_REPLACED
static void finish_security_class(UCHAR** acl_ptr,
SecurityClass::flags_t public_priv,
UCharBuffer& start_ptr,
ULONG* length_ptr)
{
/**************************************
*
* f i n i s h _ s e c u r i t y _ c l a s s
*
**************************************
*
* Functional description
* Finish off a security class, putting
* in a wildcard for any public privileges.
*
**************************************/
UCHAR* acl = *acl_ptr;
if (public_priv) {
CHECK_AND_MOVE(acl, ACL_id_list, start_ptr, length_ptr);
SCL_move_priv(&acl, public_priv, start_ptr, length_ptr);
}
CHECK_AND_MOVE(acl, ACL_end, start_ptr, length_ptr);
*acl_ptr = acl;
}
static SecurityClass::flags_t get_public_privs(
thread_db* tdbb,
const TEXT* object_name,
SSHORT obj_type)
{
/**************************************
*
* g e t _ p u b l i c _ p r i v s
*
**************************************
*
* Functional description
* Get public privileges for a particular object.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->tdbb_database;
SecurityClass::flags_t public_priv = 0;
jrd_req* request = CMP_find_request(tdbb, irq_grant5, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
PRV IN RDB$USER_PRIVILEGES
WITH PRV.RDB$RELATION_NAME EQ object_name AND
PRV.RDB$OBJECT_TYPE EQ obj_type AND
PRV.RDB$USER EQ "PUBLIC" AND
PRV.RDB$USER_TYPE EQ obj_user AND
PRV.RDB$FIELD_NAME MISSING
if (!REQUEST(irq_grant5))
REQUEST(irq_grant5) = request;
public_priv |= trans_sql_priv(PRV.RDB$PRIVILEGE);
END_FOR;
if (!REQUEST(irq_grant5))
REQUEST(irq_grant5) = request;
return public_priv;
}
static void get_object_info(thread_db* tdbb,
const TEXT* object_name,
SSHORT obj_type,
Firebird::MetaName& owner,
Firebird::MetaName& s_class,
Firebird::MetaName& default_class,
bool& view)
{
/**************************************
*
* g e t _ o b j e c t _ i n f o
*
**************************************
*
* Functional description
* This could be done in MET_scan_relation () or MET_lookup_procedure,
* but presumably we wish to make sure the information we have is
* up-to-the-minute.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->tdbb_database;
owner = s_class = default_class = "";
view = false;
if (obj_type == obj_relation) {
jrd_req* request = CMP_find_request(tdbb, irq_grant1, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
REL IN RDB$RELATIONS WITH
REL.RDB$RELATION_NAME EQ object_name
if (!REQUEST(irq_grant1))
REQUEST(irq_grant1) = request;
s_class = REL.RDB$SECURITY_CLASS;
default_class = REL.RDB$DEFAULT_CLASS;
owner = REL.RDB$OWNER_NAME;
view = !REL.RDB$VIEW_BLR.isEmpty();
END_FOR;
if (!REQUEST(irq_grant1))
REQUEST(irq_grant1) = request;
}
else {
jrd_req* request = CMP_find_request(tdbb, irq_grant9, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
REL IN RDB$PROCEDURES WITH
REL.RDB$PROCEDURE_NAME EQ object_name
if (!REQUEST(irq_grant9))
REQUEST(irq_grant9) = request;
s_class = REL.RDB$SECURITY_CLASS;
default_class = "";
owner = REL.RDB$OWNER_NAME;
view = false;
END_FOR;
if (!REQUEST(irq_grant9))
REQUEST(irq_grant9) = request;
}
}
static void get_user_privs(thread_db* tdbb,
UCHAR** acl_ptr,
const TEXT* object_name,
SSHORT obj_type,
const Firebird::MetaName& owner,
SecurityClass::flags_t public_priv,
UCharBuffer& start_ptr,
ULONG* length_ptr)
{
/**************************************
*
* g e t _ u s e r _ p r i v s
*
**************************************
*
* Functional description
* Get privileges for a particular object.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->tdbb_database;
UCHAR* acl = *acl_ptr;
Firebird::MetaName user;
SSHORT user_type = -2;
SecurityClass::flags_t priv = 0;
jrd_req* request = CMP_find_request(tdbb, irq_grant2, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
PRV IN RDB$USER_PRIVILEGES
WITH PRV.RDB$RELATION_NAME EQ object_name AND
PRV.RDB$OBJECT_TYPE EQ obj_type AND
(PRV.RDB$USER NE "PUBLIC" OR PRV.RDB$USER_TYPE NE obj_user) AND
(PRV.RDB$USER NE owner.c_str() OR PRV.RDB$USER_TYPE NE obj_user) AND
PRV.RDB$FIELD_NAME MISSING
SORTED BY PRV.RDB$USER, PRV.RDB$USER_TYPE
if (!REQUEST(irq_grant2))
REQUEST(irq_grant2) = request;
fb_utils::exact_name_limit(PRV.RDB$USER, sizeof(PRV.RDB$USER));
if (user != PRV.RDB$USER || user_type != PRV.RDB$USER_TYPE)
{
if (user.length())
{
grant_user(&acl, user, user_type, priv, start_ptr, length_ptr);
}
user_type = PRV.RDB$USER_TYPE;
if (user_type == obj_user)
{
priv = public_priv;
}
else
{
priv = 0;
}
user = PRV.RDB$USER;
}
priv |= trans_sql_priv(PRV.RDB$PRIVILEGE);
END_FOR;
if (!REQUEST(irq_grant2))
REQUEST(irq_grant2) = request;
if (user.length())
grant_user(&acl, user, user_type, priv, start_ptr, length_ptr);
*acl_ptr = acl;
}
static void grant_user(UCHAR** acl_ptr,
const Firebird::MetaName& user,
SSHORT user_type,
SecurityClass::flags_t privs,
UCharBuffer& start_ptr,
ULONG* length_ptr)
{
/**************************************
*
* g r a n t _ u s e r
*
**************************************
*
* Functional description
* Grant privileges to a particular user.
*
**************************************/
UCHAR* acl = *acl_ptr;
CHECK_AND_MOVE(acl, ACL_id_list, start_ptr, length_ptr);
switch (user_type) {
case obj_user_group:
CHECK_AND_MOVE(acl, id_group, start_ptr, length_ptr);
break;
case obj_sql_role:
CHECK_AND_MOVE(acl, id_sql_role, start_ptr, length_ptr);
break;
case obj_user:
CHECK_AND_MOVE(acl, id_person, start_ptr, length_ptr);
break;
case obj_procedure:
CHECK_AND_MOVE(acl, id_procedure, start_ptr, length_ptr);
break;
case obj_trigger:
CHECK_AND_MOVE(acl, id_trigger, start_ptr, length_ptr);
break;
case obj_view:
CHECK_AND_MOVE(acl, id_view, start_ptr, length_ptr);
break;
default:
BUGCHECK(292); /* Illegal user_type */
}
const UCHAR length = user.length();
CHECK_AND_MOVE(acl, length, start_ptr, length_ptr);
if (length) {
CHECK_ACL_BOUND(acl, start_ptr, length_ptr, length);
MOVE_FAST(user.c_str(), acl, length);
acl += length;
}
SCL_move_priv(&acl, privs, start_ptr, length_ptr);
*acl_ptr = acl;
}
#ifdef NOT_USED_OR_REPLACED
static void grant_views(
UCHAR** acl_ptr,
SecurityClass::flags_t privs, UCharBuffer& start_ptr,
ULONG* length_ptr)
{
/**************************************
*
* g r a n t _ v i e w s
*
**************************************
*
* Functional description
* Grant privileges to all views.
*
**************************************/
UCHAR* acl = *acl_ptr;
CHECK_AND_MOVE(acl, ACL_id_list, start_ptr, length_ptr);
CHECK_AND_MOVE(acl, id_views, start_ptr, length_ptr);
CHECK_AND_MOVE(acl, 0, start_ptr, length_ptr);
SCL_move_priv(&acl, privs, start_ptr, length_ptr);
*acl_ptr = acl;
}
static void purge_default_class( TEXT* object_name, SSHORT obj_type)
{
/**************************************
*
* p u r g e _ d e f a u l t _ c l a s s
*
**************************************
*
* Functional description
* Get rid of any previously defined
* default security class for this relation.
*
**************************************/
thread_db* tdbb = JRD_get_thread_data();
Database* dbb = tdbb->tdbb_database;
jrd_req* request = CMP_find_request(tdbb, irq_grant8, IRQ_REQUESTS);
FOR(REQUEST_HANDLE request)
REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ object_name
AND REL.RDB$DEFAULT_CLASS NOT MISSING
if (!REQUEST(irq_grant8))
REQUEST(irq_grant8) = request;
fb_utils::exact_name_limit(REL.RDB$DEFAULT_CLASS, sizeof(REL.RDB$DEFAULT_CLASS));
delete_security_class(tdbb, REL.RDB$DEFAULT_CLASS);
MODIFY REL USING
REL.RDB$DEFAULT_CLASS.NULL = TRUE;
END_MODIFY;
END_FOR;
if (!REQUEST(irq_grant8))
REQUEST(irq_grant8) = request;
dsc desc;
desc.dsc_dtype = dtype_text;
desc.dsc_sub_type = 0;
desc.dsc_scale = 0;
desc.dsc_ttype() = ttype_metadata;
desc.dsc_address = (UCHAR *) object_name;
desc.dsc_length = strlen(object_name);
DFW_post_work(dbb->dbb_sys_trans, dfw_scan_relation, &desc, 0);
}
#endif // NOT_USED_OR_REPLACED
static SecurityClass::flags_t save_field_privileges(thread_db* tdbb,
UCharBuffer& str_relation_buffer,
UCHAR** acl_ptr,
const TEXT* relation_name,
const Firebird::MetaName& owner,
SecurityClass::flags_t public_priv,
ULONG* length_ptr)
{
/**************************************
*
* s a v e _ f i e l d _ p r i v i l e g e s
*
**************************************
*
* Functional description
* Compute the privileges for all fields within a relation.
* All fields must be given the initial relation-level privileges.
* Conversely, field-level privileges must be added to the relation
* security class to be effective.
*
**************************************/
UCharBuffer str_field_buffer;
UCharBuffer str_field_buffer_start;
SET_TDBB(tdbb);
Database* dbb = tdbb->tdbb_database;
// initialize the field-level acl buffer to include all relation-level privs
str_field_buffer_start.getBuffer(*length_ptr - 1);
str_field_buffer.getBuffer(*length_ptr - 1);
ULONG field_length, start_length;
field_length = start_length = *length_ptr;
ULONG* field_length_ptr = &field_length;
MOVE_FAST(str_relation_buffer.begin(), str_field_buffer.begin(),
(int) (*acl_ptr - str_relation_buffer.begin()));
const int field_buffer_start_index = (int) (*acl_ptr - str_relation_buffer.begin());
UCHAR* field_acl = str_field_buffer.begin() + field_buffer_start_index;
UCHAR* relation_acl = str_relation_buffer.begin() + field_buffer_start_index;
int i;
/* remember this starting point for subsequent fields. */
for (i = 0; field_buffer_start_index > i;
i++, str_field_buffer_start[i] = str_field_buffer[i]);
Firebird::MetaName field_name, user, s_class;
SecurityClass::flags_t aggregate_public = public_priv;
SecurityClass::flags_t priv, field_public;
SSHORT user_type = -1;
jrd_req* request = CMP_find_request(tdbb, irq_grant6, IRQ_REQUESTS);
jrd_req* request2 = NULL;
FOR(REQUEST_HANDLE request)
FLD IN RDB$RELATION_FIELDS CROSS
PRV IN RDB$USER_PRIVILEGES
OVER RDB$RELATION_NAME, RDB$FIELD_NAME
WITH PRV.RDB$OBJECT_TYPE EQ obj_relation AND
PRV.RDB$RELATION_NAME EQ relation_name AND
PRV.RDB$FIELD_NAME NOT MISSING AND
(PRV.RDB$USER NE owner.c_str() OR PRV.RDB$USER_TYPE NE obj_user)
SORTED BY PRV.RDB$FIELD_NAME, PRV.RDB$USER
if (!REQUEST(irq_grant6))
REQUEST(irq_grant6) = request;
fb_utils::exact_name_limit(PRV.RDB$USER, sizeof(PRV.RDB$USER));
fb_utils::exact_name_limit(PRV.RDB$FIELD_NAME, sizeof(PRV.RDB$FIELD_NAME));
/* create a control break on field_name,user */
if (user != PRV.RDB$USER || field_name != PRV.RDB$FIELD_NAME)
{
/* flush out information for old user */
if (user.length())
{
if (user != "PUBLIC")
{
const SecurityClass::flags_t field_priv =
public_priv |
priv |
squeeze_acl(str_field_buffer.begin(),
&field_acl,
user,
user_type);
grant_user(&field_acl,
user,
user_type,
field_priv,
str_field_buffer,
field_length_ptr);
const SecurityClass::flags_t relation_priv =
public_priv |
priv |
squeeze_acl(str_relation_buffer.begin(),
&relation_acl,
user,
user_type);
grant_user(&relation_acl,
user,
user_type,
relation_priv,
str_relation_buffer,
length_ptr);
}
else
{
field_public = field_public | public_priv | priv;
}
}
/* initialize for new user */
priv = 0;
user = PRV.RDB$USER;
user_type = PRV.RDB$USER_TYPE;
}
/* create a control break on field_name */
if (field_name != PRV.RDB$FIELD_NAME) {
/* finish off the last field, adding a wildcard at end, giving PUBLIC
all privileges available at the table level as well as those
granted at the field level */
if (field_name.length()) {
aggregate_public |= field_public;
finish_security_class(&field_acl,
(field_public | public_priv),
str_field_buffer,
field_length_ptr);
save_security_class(
tdbb,
s_class,
str_field_buffer.begin(),
(USHORT)(field_acl - str_field_buffer.begin()));
}
/* initialize for new field */
field_name = PRV.RDB$FIELD_NAME;
fb_utils::exact_name_limit(FLD.RDB$SECURITY_CLASS, sizeof(FLD.RDB$SECURITY_CLASS));
s_class = FLD.RDB$SECURITY_CLASS;
if (s_class.length() == 0) {
/* We should never get here (I think) because this
value is set by dyn.e when the rdb$user_privileges
record is stored. There's also a before store trigger
on rdb$user_privileges, but it isn't so smart. -- AWH
*/
s_class.printf("%s%" QUADFORMAT "d", "SQL$GRANT",
DPM_gen_id(tdbb, MET_lookup_generator(tdbb, "RDB$SECURITY_CLASS"),
false, (SINT64) 1));
FOR(REQUEST_HANDLE request2)
FLD2 IN RDB$RELATION_FIELDS WITH
FLD2.RDB$RELATION_NAME EQ FLD.RDB$RELATION_NAME
AND FLD2.RDB$FIELD_NAME EQ FLD.RDB$FIELD_NAME
MODIFY FLD2
jrd_vtof(s_class.c_str(), FLD2.RDB$SECURITY_CLASS,
sizeof(FLD2.RDB$SECURITY_CLASS));
END_MODIFY;
END_FOR;
}
field_public = 0;
/* restart a security class at the end of the relation-level privs */
i = 0;
while (field_buffer_start_index > i)
{
++i;
str_field_buffer[i] = str_field_buffer_start[i];
}
field_acl = str_field_buffer.begin() + field_buffer_start_index;
}
priv |= trans_sql_priv(PRV.RDB$PRIVILEGE);
END_FOR;
if (!REQUEST(irq_grant6))
REQUEST(irq_grant6) = request;
if (request2)
CMP_release(tdbb, request2);
/* flush out the last user's info */
if (user.length())
{
if (user != "PUBLIC")
{
const SecurityClass::flags_t field_priv =
public_priv |
priv |
squeeze_acl(str_field_buffer.begin(),
&field_acl,
user,
user_type);
grant_user(&field_acl,
user,
user_type,
field_priv,
str_field_buffer,
field_length_ptr);
const SecurityClass::flags_t relation_priv =
public_priv |
priv |
squeeze_acl(str_relation_buffer.begin(),
&relation_acl,
user,
user_type);
grant_user(&relation_acl,
user,
user_type,
relation_priv,
str_relation_buffer,
length_ptr);
}
else
{
field_public = field_public | public_priv | priv;
}
}
/* flush out the last field's info, and schedule a format update */
if (field_name.length())
{
aggregate_public |= field_public;
finish_security_class(&field_acl,
(field_public | public_priv),
str_field_buffer,
field_length_ptr);
save_security_class(tdbb,
s_class,
str_field_buffer.begin(),
(USHORT)(field_acl - str_field_buffer.begin()));
dsc desc;
desc.dsc_dtype = dtype_text;
desc.dsc_sub_type = 0;
desc.dsc_scale = 0;
desc.dsc_ttype() = ttype_metadata;
desc.dsc_address = (UCHAR *) relation_name;
desc.dsc_length = strlen(relation_name);
DFW_post_work(dbb->dbb_sys_trans, dfw_update_format, &desc, 0);
}
*acl_ptr = relation_acl;
return aggregate_public;
}
static void save_security_class(thread_db* tdbb,
const Firebird::MetaName& s_class,
const UCHAR* buffer,
USHORT length)
{
/**************************************
*
* s a v e _ s e c u r i t y _ c l a s s
*
**************************************
*
* Functional description
* Store or update the named security class.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->tdbb_database;
bid blob_id;
blb* blob = BLB_create(tdbb, dbb->dbb_sys_trans, &blob_id);
BLB_put_segment(tdbb, blob, buffer, length);
BLB_close(tdbb, blob);
jrd_req* request = CMP_find_request(tdbb, irq_grant3, IRQ_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request)
CLS IN RDB$SECURITY_CLASSES
WITH CLS.RDB$SECURITY_CLASS EQ s_class.c_str()
if (!REQUEST(irq_grant3))
REQUEST(irq_grant3) = request;
found = true;
MODIFY CLS
CLS.RDB$ACL = blob_id;
END_MODIFY;
END_FOR;
if (!REQUEST(irq_grant3))
REQUEST(irq_grant3) = request;
if (!found) {
request = CMP_find_request(tdbb, irq_grant4, IRQ_REQUESTS);
STORE(REQUEST_HANDLE request)
CLS IN RDB$SECURITY_CLASSES
jrd_vtof(s_class.c_str(), CLS.RDB$SECURITY_CLASS,
sizeof(CLS.RDB$SECURITY_CLASS));
CLS.RDB$ACL = blob_id;
END_STORE;
if (!REQUEST(irq_grant4))
REQUEST(irq_grant4) = request;
}
}
static SecurityClass::flags_t trans_sql_priv(const TEXT* privileges)
{
/**************************************
*
* t r a n s _ s q l _ p r i v
*
**************************************
*
* Functional description
* Map a SQL privilege letter into an internal privilege bit.
*
**************************************/
SecurityClass::flags_t priv = 0;
switch (UPPER7(privileges[0])) {
case 'S':
priv |= SCL_read;
break;
case 'I':
priv |= SCL_sql_insert;
break;
case 'U':
priv |= SCL_sql_update;
break;
case 'D':
priv |= SCL_sql_delete;
break;
case 'R':
priv |= SCL_sql_references;
break;
case 'X':
priv |= SCL_execute;
break;
}
return priv;
}
static SecurityClass::flags_t squeeze_acl(UCHAR* acl_base,
UCHAR** acl_ptr,
const Firebird::MetaName& user,
SSHORT user_type)
{
/**************************************
*
* s q u e e z e _ a c l
*
**************************************
*
* Functional description
* Walk an access control list looking for a hit. If a hit
* is found, return privileges and squeeze out that acl-element.
* The caller will use the returned privilege to insert a new
* privilege for the input user.
*
**************************************/
UCHAR* dup_acl;
SecurityClass::flags_t privilege = 0;
UCHAR c;
/* Make sure that this half-finished acl looks good enough to process. */
**acl_ptr = 0;
UCHAR* acl = acl_base;
if (*acl++ != ACL_version)
BUGCHECK(160); /* msg 160 wrong ACL version */
bool hit = false;
while ( (c = *acl++) )
switch (c) {
case ACL_id_list:
dup_acl = acl - 1;
hit = true;
while ( (c = *acl++) ) {
switch (c) {
case id_person:
if (user_type != obj_user)
hit = false;
if (check_string(acl, user))
hit = false;
break;
case id_sql_role:
if (user_type != obj_sql_role)
hit = false;
if (check_string(acl, user))
hit = false;
break;
case id_view:
if (user_type != obj_view)
hit = false;
if (check_string(acl, user))
hit = false;
break;
case id_procedure:
if (user_type != obj_procedure)
hit = false;
if (check_string(acl, user))
hit = false;
break;
case id_trigger:
if (user_type != obj_trigger)
hit = false;
if (check_string(acl, user))
hit = false;
break;
case id_project:
case id_organization:
hit = false;
// CVC: What's the idea of calling a function whose only
// result is boolean without checking it?
check_string(acl, user);
break;
case id_views:
hit = false;
break;
case id_node:
case id_user:
{
hit = false;
for (UCHAR l = *acl++; l; acl++, l--);
break;
}
case id_group:
if (user_type != obj_user_group)
hit = false;
if (check_string(acl, user))
hit = false;
break;
default:
BUGCHECK(293); /* bad ACL */
}
acl += *acl + 1;
}
break;
case ACL_priv_list:
if (hit) {
while ( (c = *acl++) )
switch (c) {
case priv_control:
privilege |= SCL_control;
break;
case priv_read:
privilege |= SCL_read;
break;
case priv_write:
privilege |= SCL_write;
break;
case priv_sql_insert:
privilege |= SCL_sql_insert;
break;
case priv_sql_delete:
privilege |= SCL_sql_delete;
break;
case priv_sql_references:
privilege |= SCL_sql_references;
break;
case priv_sql_update:
privilege |= SCL_sql_update;
break;
case priv_delete:
privilege |= SCL_delete;
break;
case priv_grant:
privilege |= SCL_grant;
break;
case priv_protect:
privilege |= SCL_protect;
break;
case priv_execute:
privilege |= SCL_execute;
break;
default:
BUGCHECK(293); /* bad ACL */
}
/* Squeeze out duplicate acl element. */
UCHAR* dest = dup_acl;
const UCHAR* source = acl;
int length = *acl_ptr - source + 1; // ptrdiff_t
*acl_ptr = *acl_ptr - (source - dest);
acl = dup_acl;
for (; length; *dest++ = *source++, length--);
}
else
while (*acl++);
break;
default:
BUGCHECK(293); /* bad ACL */
}
return privilege;
}
static bool check_string(const UCHAR* acl, const Firebird::MetaName& name)
{
/**************************************
*
* c h e c k _ s t r i n g
*
**************************************
*
* Functional description
* Check a string against an acl string. If they don't match,
* return true.
*
**************************************/
/* JPN: Since Kanji User names are not allowed, No need to fix this UPPER loop. */
USHORT l = *acl++;
const TEXT* string = name.c_str();
if (l)
{
do
{
const UCHAR c1 = *acl++;
const TEXT c2 = *string++;
if (UPPER7(c1) != UPPER7(c2))
{
return true;
}
} while (--l);
}
return (*string && *string != ' ') ? true : false;
}