/* * 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 #include #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/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; 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) { 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())); } 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%" SQUADFORMAT, 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; jrd_req* request3 = 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; s_class = FLD.RDB$SECURITY_CLASS; if (FLD.RDB$SECURITY_CLASS.NULL || s_class.length() == 0) { bool unique = false; FOR(REQUEST_HANDLE request2) RFR IN RDB$RELATION_FIELDS WITH RFR.RDB$RELATION_NAME EQ FLD.RDB$RELATION_NAME AND RFR.RDB$FIELD_NAME EQ FLD.RDB$FIELD_NAME MODIFY RFR while (!unique) { sprintf(RFR.RDB$SECURITY_CLASS, "%s%" SQUADFORMAT, "SQL$GRANT", DPM_gen_id(tdbb, MET_lookup_generator(tdbb, "RDB$SECURITY_CLASS"), false, (SINT64) 1)); unique = true; FOR (REQUEST_HANDLE request3) RFR2 IN RDB$RELATION_FIELDS WITH RFR2.RDB$SECURITY_CLASS = RFR.RDB$SECURITY_CLASS unique = false; END_FOR; } RFR.RDB$SECURITY_CLASS.NULL = FALSE; s_class = RFR.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); if (request3) CMP_release(tdbb, request3); /* 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; }