/*
 *	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/common.h"
#include "../jrd/ibase.h"
#include "../jrd/jrd.h"
#include "../jrd/ods.h"
#include "../jrd/scl.h"
#include "../jrd/jrd_pwd.h"
#include "../jrd/acl.h"
#include "../jrd/blb.h"
#include "../jrd/irq.h"
#include "../jrd/obj.h"
#include "../jrd/req.h"
#include "../jrd/tra.h"
#include "../jrd/gdsassert.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/enc_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/isc_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/grant_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/constants.h"
#include "../include/fb_exception.h"
#include "../common/utils_proto.h"
#include "../common/classes/array.h"
#include "../common/config/config.h"
#include "../jrd/os/os_utils.h"


const int UIC_BASE	= 10;

using namespace Jrd;
using namespace Firebird;

DATABASE DB = FILENAME "ODS.RDB";

static bool check_hex(const UCHAR*, USHORT);
static bool check_number(const UCHAR*, USHORT);
static bool check_user_group(thread_db* tdbb, const UCHAR*, USHORT);
static bool check_string(const UCHAR*, const Firebird::MetaName&);
static SecurityClass::flags_t compute_access(thread_db* tdbb, const SecurityClass*,
	const jrd_rel*,	const Firebird::MetaName&, const Firebird::MetaName&);
static SecurityClass::flags_t walk_acl(thread_db* tdbb, const Acl&, const jrd_rel*,
	const Firebird::MetaName&, const Firebird::MetaName&);

static inline void check_and_move(UCHAR from, Acl& to)
{
	to.push(from);
}

struct P_NAMES {
	SecurityClass::flags_t p_names_priv;
	USHORT p_names_acl;
	const TEXT* p_names_string;
};

static const P_NAMES p_names[] =
{
	{ SCL_protect, priv_protect, "protect" },
	{ SCL_control, priv_control, "control" },
	{ SCL_delete, priv_delete, "delete" },
	{ SCL_sql_insert, priv_sql_insert, "insert/write" },
	{ SCL_sql_update, priv_sql_update, "update/write" },
	{ SCL_sql_delete, priv_sql_delete, "delete/write" },
	{ SCL_write, priv_write, "write" },
	{ SCL_read, priv_read, "read/select" },
	{ SCL_grant, priv_grant, "grant" },
	{ SCL_sql_references, priv_sql_references, "references" },
	{ SCL_execute, priv_execute, "execute" },
	{ 0, 0, "" }
};


void SCL_check_access(thread_db* tdbb,
					  const SecurityClass* s_class,
					  SLONG view_id,
					  const Firebird::MetaName& trg_name,
					  const Firebird::MetaName& prc_name,
					  SecurityClass::flags_t mask,
					  const TEXT* type,
					  const Firebird::MetaName& name,
					  const Firebird::MetaName& r_name)
{
/**************************************
 *
 *	S C L _ c h e c k _ a c c e s s
 *
 **************************************
 *
 * Functional description
 *	Check security class for desired permission.  Check first that
 *	the desired access has been granted to the database then to the
 *	object in question.
 *
 **************************************/
	SET_TDBB(tdbb);

	if (s_class && (s_class->scl_flags & SCL_corrupt))
	{
		ERR_post(Arg::Gds(isc_no_priv) << Arg::Str("(ACL unrecognized)") <<
										  Arg::Str("security_class") <<
										  Arg::Str(s_class->scl_name));
	}

	const Attachment& attachment = *tdbb->getAttachment();

	// Allow the database owner to back up a database even if he does not have
	// read access to all the tables in the database

	if ((attachment.att_flags & ATT_gbak_attachment) && (mask & SCL_read))
	{
		return;
	}

	// Allow the locksmith any access to database

	if (attachment.locksmith())
	{
		return;
	}

	bool denied_db = false;

	const SecurityClass* const att_class = attachment.att_security_class;
	if (att_class && !(att_class->scl_flags & mask))
	{
		denied_db = true;
	}
	else
	{
		if (!s_class ||	(mask & s_class->scl_flags)) {
			return;
		}
		const jrd_rel* view = NULL;
		if (view_id) {
			view = MET_lookup_relation_id(tdbb, view_id, false);
		}
		if ((view || trg_name.length() || prc_name.length()) &&
			 (compute_access(tdbb, s_class, view, trg_name, prc_name) & mask))
		{
			return;
		}
	}

	const P_NAMES* names;
	for (names = p_names; names->p_names_priv; names++)
	{
		if (names->p_names_priv & mask)
		{
			break;
		}
	}

	if (denied_db)
	{
		ERR_post(Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
										  Arg::Str(object_database) <<
										  Arg::Str(""));
	}
	else
	{
		const Firebird::string full_name = 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(type) <<
										  Arg::Str(full_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);
	Database* dbb = tdbb->getDatabase();
	const SecurityClass* s_class = NULL;
	const SecurityClass* default_s_class = NULL;

	// No security to check for if the index is not yet created

    if ((index_name.length() == 0) && (index_id < 1)) {
        return;
    }

    Firebird::MetaName reln_name, aux_idx_name;
    const Firebird::MetaName* idx_name_ptr = &index_name;
	const Firebird::MetaName* relation_name_ptr = &index_name;

	jrd_req* request = NULL;

	// No need to cache this request handle, it's only used when
	// new constraints are created

    if (index_id < 1) {
        FOR(REQUEST_HANDLE request) IND IN RDB$INDICES
            CROSS REL IN RDB$RELATIONS
			OVER RDB$RELATION_NAME
			WITH IND.RDB$INDEX_NAME EQ index_name.c_str()

            reln_name = REL.RDB$RELATION_NAME;
		    if (!REL.RDB$SECURITY_CLASS.NULL)
                s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
            if (!REL.RDB$DEFAULT_CLASS.NULL)
                default_s_class = SCL_get_class(tdbb, REL.RDB$DEFAULT_CLASS);
        END_FOR;

        CMP_release(tdbb, request);
    }
    else {
        idx_name_ptr = &aux_idx_name;
        FOR (REQUEST_HANDLE request) IND IN RDB$INDICES
            CROSS REL IN RDB$RELATIONS
            OVER RDB$RELATION_NAME
            WITH IND.RDB$RELATION_NAME EQ relation_name_ptr->c_str()
            AND IND.RDB$INDEX_ID EQ index_id

            reln_name = REL.RDB$RELATION_NAME;
            aux_idx_name = IND.RDB$INDEX_NAME;
            if (!REL.RDB$SECURITY_CLASS.NULL)
                s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
            if (!REL.RDB$DEFAULT_CLASS.NULL)
                default_s_class = SCL_get_class(tdbb, REL.RDB$DEFAULT_CLASS);
        END_FOR;

        CMP_release (tdbb, request);
    }

	// Check if the relation exists. It may not have been created yet.
	// Just return in that case.

	if (reln_name.length() == 0) {
		return;
	}

	SCL_check_access(tdbb, s_class, 0, NULL, NULL, mask, object_table, reln_name);

	request = NULL;

	// Set up the exception mechanism, so that we can release the request
	// in case of error in SCL_check_access

	try {

	// Check if the field used in the index has the appropriate
	// permission. If the field in question does not have a security class
	// defined, then the default security class for the table applies for that
	// field.

	// No need to cache this request handle, it's only used when
	// new constraints are created

	FOR(REQUEST_HANDLE request) ISEG IN RDB$INDEX_SEGMENTS
		CROSS RF IN RDB$RELATION_FIELDS
			OVER RDB$FIELD_NAME
			WITH RF.RDB$RELATION_NAME EQ reln_name.c_str()
			AND ISEG.RDB$INDEX_NAME EQ idx_name_ptr->c_str()

		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, NULL, NULL, mask,
						 object_column, RF.RDB$FIELD_NAME, reln_name);

	END_FOR;

	CMP_release(tdbb, request);
	}
	catch (const Firebird::Exception&) {
		if (request) {
			CMP_release(tdbb, request);
		}
		throw;
	}
}


void SCL_check_procedure(thread_db* tdbb, const dsc* dsc_name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *	Given a procedure name, check for a set of privileges.  The
 *	procedure in question may or may not have been created, let alone
 *	scanned.  This is used exclusively for meta-data operations.
 *
 **************************************/
	SET_TDBB(tdbb);

	// Get the name in CSTRING format, ending on NULL or SPACE
	fb_assert(dsc_name->dsc_dtype == dtype_text);
	const Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
							dsc_name->dsc_length);

	Database* dbb = tdbb->getDatabase();
	const SecurityClass* s_class = NULL;

	jrd_req* request = CMP_find_request(tdbb, irq_p_security, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) SPROC IN RDB$PROCEDURES
		WITH SPROC.RDB$PROCEDURE_NAME EQ name.c_str()

        if (!REQUEST(irq_p_security))
			REQUEST(irq_p_security) = request;

		if (!SPROC.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, SPROC.RDB$SECURITY_CLASS);
	END_FOR;

	if (!REQUEST(irq_p_security))
		REQUEST(irq_p_security) = request;

	SCL_check_access(tdbb, s_class, 0, NULL, name, mask, object_procedure, name);
}


void SCL_check_relation(thread_db* tdbb, const dsc* dsc_name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Given a relation name, check for a set of privileges.  The
 *	relation in question may or may not have been created, let alone
 *	scanned.  This is used exclusively for meta-data operations.
 *
 **************************************/
	SET_TDBB(tdbb);

	// Get the name in CSTRING format, ending on NULL or SPACE
	fb_assert(dsc_name->dsc_dtype == dtype_text);
	const Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
							dsc_name->dsc_length);

	Database* dbb = tdbb->getDatabase();

	const SecurityClass* s_class = NULL;

	jrd_req* request = CMP_find_request(tdbb, irq_v_security, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS
		WITH REL.RDB$RELATION_NAME EQ name.c_str()

        if (!REQUEST(irq_v_security))
			REQUEST(irq_v_security) = request;

        if (!REL.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
	END_FOR;

	if (!REQUEST(irq_v_security))
		REQUEST(irq_v_security) = request;

	SCL_check_access(tdbb, s_class, 0, NULL, NULL, mask, object_table, name);
}


SecurityClass* SCL_get_class(thread_db* tdbb, const TEXT* 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);
	//Database* dbb = tdbb->getDatabase();

	// Name may be absent or terminated with NULL or blank.  Clean up name.

	if (!par_string) {
		return NULL;
	}

	Firebird::MetaName string(par_string);

	//fb_utils::exact_name(string);

	if (string.isEmpty()) {
		return NULL;
	}

	Attachment* attachment = tdbb->getAttachment();

	// Look for the class already known

	SecurityClassList* list = attachment->att_security_classes;
	if (list && list->locate(string))
		return list->current();

	// Class isn't known. So make up a new security class block.

	MemoryPool& pool = *attachment->att_pool;

	SecurityClass* const s_class = FB_NEW(pool) SecurityClass(pool, string);
	s_class->scl_flags = compute_access(tdbb, s_class, NULL, NULL, NULL);

	if (s_class->scl_flags & SCL_exists)
	{
		if (!list) {
			attachment->att_security_classes = list = FB_NEW(pool) SecurityClassList(pool);
		}

		list->add(s_class);
		return s_class;
	}

	delete s_class;

	return NULL;
}


SecurityClass::flags_t SCL_get_mask(thread_db* tdbb, const TEXT* relation_name, const TEXT* field_name)
{
/**************************************
 *
 *	S C L _ g e t _ m a s k
 *
 **************************************
 *
 * Functional description
 *	Get a protection mask for a named object.  If field and
 *	relation names are present, get access to field.  If just
 *	relation name, get access to relation.  If neither, get
 *	access for database.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// Start with database security class

	const SecurityClass* s_class = attachment->att_security_class;
	SecurityClass::flags_t access = s_class ? s_class->scl_flags : -1;

	// If there's a relation, track it down
	jrd_rel* relation;
	if (relation_name && (relation = MET_lookup_relation(tdbb, relation_name)))
	{
		MET_scan_relation(tdbb, relation);
		if ( (s_class = SCL_get_class(tdbb, relation->rel_security_name.c_str())) )
		{
			access &= s_class->scl_flags;
		}

		const jrd_fld* field;
		SSHORT id;
		if (field_name &&
			(id = MET_lookup_field(tdbb, relation, field_name)) >= 0 &&
			(field = MET_get_field(relation, id)) &&
			(s_class = SCL_get_class(tdbb, field->fld_security_name.c_str())))
		{
			access &= s_class->scl_flags;
		}
	}

	return access & (SCL_read | SCL_write | SCL_delete | SCL_control |
					 SCL_grant | SCL_sql_insert | SCL_sql_update |
					 SCL_sql_delete | SCL_protect | SCL_sql_references |
					 SCL_execute);
}


void SCL_init(thread_db* tdbb, bool create, const UserId& tempId)
{
/**************************************
 *
 *	S C L _ i n i t
 *
 **************************************
 *
 * Functional description
 *	Check database access control list.
 *
 *	Checks the userinfo database to get the
 *	password and other stuff about the specified
 *	user.   Compares the password to that passed
 *	in, encrypting if necessary.
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* dbb = tdbb->getDatabase();
	const USHORT major_version = dbb->dbb_ods_version;
	const USHORT minor_original = dbb->dbb_minor_original;

	const TEXT* sql_role = tempId.usr_sql_role_name.nullStr();
	Firebird::string loginName(tempId.usr_user_name);
	loginName.upper();
	const TEXT* login_name = loginName.c_str();
	Firebird::MetaName role_name;
    // CVC: Let's clean any role in pre-ODS9 attachments
    bool preODS9 = true;

/***************************************************************
**
** skip reading system relation RDB$ROLES when attaching pre ODS_9_0 database
**
****************************************************************/

    // CVC: We'll verify the role and wipe it out when it doesn't exist

	if (ENCODE_ODS(major_version, minor_original) >= ODS_9_0) {

        preODS9 = false;

		if (strlen(login_name) != 0)
		{
			if (!create)
			{
				jrd_req* request = CMP_find_request(tdbb, irq_get_role_name, IRQ_REQUESTS);

				FOR(REQUEST_HANDLE request) X IN RDB$ROLES
					WITH X.RDB$ROLE_NAME EQ login_name

                    if (!REQUEST(irq_get_role_name))
                        REQUEST(irq_get_role_name) = request;

					EXE_unwind(tdbb, request);
					ERR_post(Arg::Gds(isc_login_same_as_role_name) << Arg::Str(login_name));

				END_FOR;

				if (!REQUEST(irq_get_role_name))
					REQUEST(irq_get_role_name) = request;
			}
		}

        // CVC: If this is ODS>=ODS_9_0 and we aren't creating a db and sql_role was specified,
        // then verify it against rdb$roles and rdb$user_privileges

        if (!create && sql_role && *sql_role && strcmp(sql_role, NULL_ROLE)) {
            bool found = false;

			if (!(tempId.usr_flags & USR_trole))
			{
	            jrd_req* request = CMP_find_request (tdbb, irq_verify_role_name, IRQ_REQUESTS);

	            // CVC: The caller has hopefully uppercased the role or stripped quotes. Of course,
    	        // uppercase-UPPER7 should only happen if the role wasn't enclosed in quotes.
        	    // Shortsighted developers named the field rdb$relation_name instead of rdb$object_name.
    	       	// This request is not exactly the same than irq_get_role_mem, sorry, I can't reuse that.
	            // If you think that an unknown role cannot be granted, think again: someone made sure
        	    // in DYN that SYSDBA can do almost anything, including invalid grants.

            	FOR (REQUEST_HANDLE request) FIRST 1 RR IN RDB$ROLES
                	CROSS UU IN RDB$USER_PRIVILEGES
	                WITH RR.RDB$ROLE_NAME        EQ sql_role
    	            AND RR.RDB$ROLE_NAME         EQ UU.RDB$RELATION_NAME
        	        AND UU.RDB$OBJECT_TYPE       EQ obj_sql_role
            	    AND (UU.RDB$USER             EQ login_name
                	     OR UU.RDB$USER          EQ "PUBLIC")
	                AND UU.RDB$USER_TYPE         EQ obj_user
    	            AND UU.RDB$PRIVILEGE         EQ "M"

        	        if (!REQUEST (irq_verify_role_name))
            	        REQUEST (irq_verify_role_name) = request;

                	if (!UU.RDB$USER.NULL)
                    	found = true;

	            END_FOR;

    	        if (!REQUEST (irq_verify_role_name))
        	        REQUEST (irq_verify_role_name) = request;
			}

			if (!found && (tempId.usr_flags & USR_trole))
			{
	            jrd_req* request = CMP_find_request (tdbb, irq_verify_trusted_role, IRQ_REQUESTS);

	            FOR (REQUEST_HANDLE request) FIRST 1 RR IN RDB$ROLES
        	        WITH RR.RDB$ROLE_NAME  EQ sql_role
            	    AND RR.RDB$SYSTEM_FLAG NOT MISSING

            	    if (!REQUEST (irq_verify_trusted_role))
	                    REQUEST (irq_verify_trusted_role) = request;

	                if (RR.RDB$SYSTEM_FLAG & ROLE_FLAG_MAY_TRUST)
	                    found = true;

	            END_FOR;

	            if (!REQUEST (irq_verify_trusted_role))
    	            REQUEST (irq_verify_trusted_role) = request;
			}

            if (!found)
			{
                role_name = NULL_ROLE;
			}
        }
    }

	if (sql_role) {
        if ((!preODS9) && (role_name != NULL_ROLE)) {
            role_name = sql_role;
        }
	}
	else {
		role_name = NULL_ROLE;
	}

	Attachment* const attachment = tdbb->getAttachment();
	MemoryPool& pool = *attachment->att_pool;
	UserId* const user = FB_NEW(pool) UserId(pool, tempId);
	user->usr_sql_role_name = role_name.c_str();
	attachment->att_user = user;

	if (!create) {
		jrd_req* handle = NULL;
		jrd_req* handle1 = NULL;
		jrd_req* handle2 = NULL;

		FOR(REQUEST_HANDLE handle) X IN RDB$DATABASE

			if (!X.RDB$SECURITY_CLASS.NULL)
				attachment->att_security_class = SCL_get_class(tdbb, X.RDB$SECURITY_CLASS);
		END_FOR;
		CMP_release(tdbb, handle);

		FOR(REQUEST_HANDLE handle1)
			FIRST 1 REL IN RDB$RELATIONS
			WITH REL.RDB$RELATION_NAME EQ "RDB$DATABASE"

            if (!REL.RDB$OWNER_NAME.NULL && user->usr_user_name.hasData())
			{
				char name[129];
				*name = user->usr_user_name.length();
				user->usr_user_name.copyTo(name + 1, sizeof(name) - 1);
				if (!check_string(reinterpret_cast<const UCHAR*>(name), REL.RDB$OWNER_NAME))
				{
					user->usr_flags |= USR_owner;
				}
			}
		END_FOR;
		CMP_release(tdbb, handle1);

		if (!preODS9)
		{
			FOR(REQUEST_HANDLE handle2) R IN RDB$ROLES
				WITH R.RDB$ROLE_NAME EQ role_name.c_str()

				if (!R.RDB$SYSTEM_FLAG.NULL)
				{
					if (R.RDB$SYSTEM_FLAG & ROLE_FLAG_DBO)
						user->usr_flags |= USR_dba;
				}
			END_FOR;
			CMP_release(tdbb, handle2);
		}
	}
	else {
		user->usr_flags |= USR_owner;
	}

}


UserId::~UserId()
{
	if (usr_fini_sec_db)
	{
		SecurityDatabase::shutdown();
	}
}


void SCL_move_priv(SecurityClass::flags_t mask, Acl& acl)
{
/**************************************
 *
 *	S C L _ m o v e _ p r i v
 *
 **************************************
 *
 * Functional description
 *	Given a mask of privileges, move privileges types to acl.
 *
 **************************************/
	// Terminate identification criteria, and move privileges

	check_and_move(ACL_end, acl);
	check_and_move(ACL_priv_list, acl);

	for (const P_NAMES* priv = p_names; priv->p_names_priv; priv++)
	{
		if (mask & priv->p_names_priv)
		{
			fb_assert(priv->p_names_acl <= MAX_UCHAR);
			check_and_move(priv->p_names_acl, acl);
		}
	}

	check_and_move(0, acl);
}


SecurityClass* SCL_recompute_class(thread_db* tdbb, const TEXT* string)
{
/**************************************
 *
 *	S C L _ r e c o m p u t e _ c l a s s
 *
 **************************************
 *
 * Functional description
 *	Something changed with a security class, recompute it.  If we
 *	can't find it, return NULL.
 *
 **************************************/
	SET_TDBB(tdbb);

	SecurityClass* s_class = SCL_get_class(tdbb, string);
	if (!s_class) {
		return NULL;
	}

	s_class->scl_flags = compute_access(tdbb, s_class, NULL, NULL, NULL);

	if (s_class->scl_flags & SCL_exists) {
		return s_class;
	}

	// Class no long exists - get rid of it!

	SecurityClassList* list = tdbb->getAttachment()->att_security_classes;
	if (list && list->locate(string))
	{
		list->fastRemove();
		delete s_class;
	}

	return NULL;
}


void SCL_release_all(SecurityClassList*& list)
{
/**************************************
 *
 *	S C L _ r e l e a s e _ a l l
 *
 **************************************
 *
 * Functional description
 *	Release all security classes.
 *
 **************************************/
	if (!list)
		return;

	if (list->getFirst())
	{
		do {
			delete list->current();
		} while (list->getNext());
	}

	delete list;
	list = NULL;
}


static bool check_hex(const UCHAR* acl, USHORT number)
{
/**************************************
 *
 *	c h e c k _ h e x
 *
 **************************************
 *
 * Functional description
 *	Check a string against and acl numeric string.  If they don't match,
 *	return true.
 *
 **************************************/
	int n = 0;
	USHORT l = *acl++;
	if (l)
	{
		do {
			const TEXT c = *acl++;
			n *= 10;
			if (c >= '0' && c <= '9') {
				n += c - '0';
			}
			else if (c >= 'a' && c <= 'f') {
				n += c - 'a' + 10;
			}
			else if (c >= 'A' && c <= 'F') {
				n += c - 'A' + 10;
			}
		} while (--l);
	}

	return (n != number);
}


static bool check_number(const UCHAR* acl, USHORT number)
{
/**************************************
 *
 *	c h e c k _ n u m b e r
 *
 **************************************
 *
 * Functional description
 *	Check a string against and acl numeric string.  If they don't match,
 *	return true.
 *
 **************************************/
	int n = 0;
	USHORT l = *acl++;
	if (l)
	{
		do {
			n = n * UIC_BASE + *acl++ - '0';
		} while (--l);
	}

	return (n != number);
}


static bool check_user_group(thread_db* tdbb, const UCHAR* acl, USHORT number)
{
/**************************************
 *
 *	c h e c k _ u s e r _ g r o u p
 *
 **************************************
 *
 * Functional description
 *
 *	Check a string against an acl numeric string.
 *
 * logic:
 *
 *  If the string contains user group name,
 *    then
 *      converts user group name to numeric user group id.
 *    else
 *      converts character user group id to numeric user group id.
 *
 *	Check numeric user group id against an acl numeric string.
 *  If they don't match, return true.
 *
 **************************************/
	SET_TDBB(tdbb);
	//Database* dbb = tdbb->getDatabase();

	SLONG n = 0;

	USHORT l = *acl++;
	if (l)
	{
		if (isdigit(*acl))	// this is a group id
		{
			do {
				n = n * UIC_BASE + *acl++ - '0';
			} while (--l);
		}
		else				// processing group name
		{
			Firebird::string user_group_name;
			do {
				const TEXT one_char = *acl++;
				user_group_name += LOWWER(one_char);
			} while (--l);

			// convert unix group name to unix group id
			n = 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 size_t length = *acl++;
	const TEXT* const ptr = (TEXT*) acl;

    return (string.compare(ptr, length) != 0);
}


static SecurityClass::flags_t compute_access(thread_db* tdbb,
											 const SecurityClass* s_class,
											 const jrd_rel* view,
											 const Firebird::MetaName& trg_name,
											 const Firebird::MetaName& prc_name)
{
/**************************************
 *
 *	c o m p u t e _ a c c e s s
 *
 **************************************
 *
 * Functional description
 *	Compute access for security class.  If a relation block is
 *	present, it is a view, and we should check for enhanced view
 *	access permissions.  Return a flag word of recognized privileges.
 *
 **************************************/
	Acl acl;

	SET_TDBB(tdbb);
	Database* dbb = tdbb->getDatabase();

	SecurityClass::flags_t privileges = SCL_scanned;

	jrd_req* request = CMP_find_request(tdbb, irq_l_security, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) X IN RDB$SECURITY_CLASSES
		WITH X.RDB$SECURITY_CLASS EQ s_class->scl_name.c_str()

        if (!REQUEST(irq_l_security))
			REQUEST(irq_l_security) = request;

		privileges |= SCL_exists;
		blb* blob = BLB_open(tdbb, dbb->dbb_sys_trans, &X.RDB$ACL);
		UCHAR* buffer = acl.getBuffer(ACL_BLOB_BUFFER_SIZE);
		UCHAR* end = buffer;
		while (true)
		{
			end += BLB_get_segment(tdbb, blob, end, (USHORT) (acl.getCount() - (end - buffer)) );
			if (blob->blb_flags & BLB_eof)
				break;

			// There was not enough space, realloc point acl to the correct location

			if (blob->blb_fragment_size)
			{
				const ptrdiff_t old_offset = end - buffer;
				buffer = acl.getBuffer(acl.getCount() + ACL_BLOB_BUFFER_SIZE);
				end = buffer + old_offset;
			}
		}
		BLB_close(tdbb, blob);
		blob = NULL;
		acl.shrink(end - buffer);

		if (acl.getCount() > 0)
		{
			privileges |= walk_acl(tdbb, acl, view, trg_name, prc_name);
		}
	END_FOR;

	if (!REQUEST(irq_l_security))
		REQUEST(irq_l_security) = request;

	return privileges;
}


static SecurityClass::flags_t walk_acl(thread_db* tdbb,
									   const Acl& acl,
									   const jrd_rel* view,
									   const Firebird::MetaName& trg_name,
									   const Firebird::MetaName& prc_name)
{
/**************************************
 *
 *	w a l k _ a c l
 *
 **************************************
 *
 * Functional description
 *	Walk an access control list looking for a hit.  If a hit
 *	is found, return privileges.
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* dbb = tdbb->getDatabase();

	// Munch ACL. If we find a hit, eat up privileges.

	UserId user = *tdbb->getAttachment()->att_user;
	const TEXT* role_name = user.usr_sql_role_name.nullStr();

	if (view && (view->rel_flags & REL_sql_relation))
	{
		// Use the owner of the view to perform the sql security
		// checks with: (1) The view user must have sufficient privileges
		// to the view, and (2a) the view owner must have sufficient
		// privileges to the base table or (2b) the view must have
		// sufficient privileges on the base table.

		user.usr_user_name = view->rel_owner_name.c_str();
	}

	SecurityClass::flags_t privilege = 0;
	const UCHAR* a = acl.begin();

	if (*a++ != ACL_version)
	{
		BUGCHECK(160);	// msg 160 wrong ACL version
	}

	if (user.locksmith())
	{
		return -1 & ~SCL_corrupt;
	}

	const TEXT* p;
	bool hit = false;
	UCHAR c;

	while ( (c = *a++) )
	{
		switch (c)
		{
		case ACL_id_list:
			hit = true;
			while ( (c = *a++) )
			{
				switch (c)
				{
				case id_person:
					if (!(p = user.usr_user_name.nullStr()) || check_string(a, p))
						hit = false;
					break;

				case id_project:
					if (!(p = user.usr_project_name.nullStr()) || check_string(a, p))
						hit = false;
					break;

				case id_organization:
					if (!(p = user.usr_org_name.nullStr()) || check_string(a, p))
						hit = false;
					break;

				case id_group:
					if (check_user_group(tdbb, a, user.usr_group_id))
					{
						hit = false;
					}
					break;

				case id_sql_role:
					if (!role_name || check_string(a, role_name))
						hit = false;
					else
					{
						TEXT login_name[129];
						TEXT* pln = login_name;
						const TEXT* q = user.usr_user_name.c_str();
						while (*pln++ = UPPER7(*q)) {
							++q;
						}
						hit = false;
						jrd_req* request = CMP_find_request(tdbb, irq_get_role_mem, IRQ_REQUESTS);

						FOR(REQUEST_HANDLE request) U IN RDB$USER_PRIVILEGES WITH
							(U.RDB$USER EQ login_name OR
							 U.RDB$USER EQ "PUBLIC") AND
								U.RDB$USER_TYPE EQ obj_user AND
								U.RDB$RELATION_NAME EQ user.usr_sql_role_name.c_str() AND
								U.RDB$OBJECT_TYPE EQ obj_sql_role AND
								U.RDB$PRIVILEGE EQ "M"

                            if (!REQUEST(irq_get_role_mem))
                                REQUEST(irq_get_role_mem) = request;

							if (!U.RDB$USER.NULL)
								hit = true;
						END_FOR;

						if (!REQUEST(irq_get_role_mem))
							REQUEST(irq_get_role_mem) = request;
					}
					break;

				case id_view:
					if (!view || check_string(a, view->rel_name))
						hit = false;
					break;

				case id_procedure:
					if (check_string(a, prc_name))
						hit = false;
					break;

				case id_trigger:
					if (check_string(a, trg_name))
					{
						hit = false;
					}
					break;

				case id_views:
					// Disable this catch-all that messes up the view security.
					// Note that this id_views is not generated anymore, this code
					// is only here for compatibility. id_views was only
					// generated for SQL.

					hit = false;
					if (!view) {
						hit = false;
					}
					break;

				case id_user:
					if (check_number(a, user.usr_user_id)) {
						hit = false;
					}
					break;

				case id_node:
					if (check_hex(a, user.usr_node_id)) {
						hit = false;
					}
					break;

				default:
					return SCL_corrupt;
				}
				a += *a + 1;
			}
			break;

		case ACL_priv_list:
			if (hit)
			{
				while ( (c = *a++) )
				{
					switch (c)
					{
					case priv_control:
						privilege |= SCL_control;
						break;

					case priv_read:
						// Note that READ access must imply REFERENCES
						// access for upward compatibility of existing
						// security classes
						privilege |= SCL_read | SCL_sql_references;
						break;

					case priv_write:
						privilege |=
							SCL_write | SCL_sql_insert | SCL_sql_update |
							SCL_sql_delete;
						break;

					case priv_sql_insert:
						privilege |= SCL_sql_insert;
						break;

					case priv_sql_delete:
						privilege |= SCL_sql_delete;
						break;

					case priv_sql_references:
						privilege |= SCL_sql_references;
						break;

					case priv_sql_update:
						privilege |= SCL_sql_update;
						break;

					case priv_delete:
						privilege |= SCL_delete;
						break;

					case priv_grant:
						privilege |= SCL_grant;
						break;

					case priv_protect:
						privilege |= SCL_protect;
						break;

					case priv_execute:
						privilege |= SCL_execute;
						break;

					default:
						return SCL_corrupt;
					}
				}

				// For a relation the first hit does not give the privilege.
				// Because, there could be some permissions for the table
				// (for user1) and some permissions for a column on that
				// table for public/user2, causing two hits.
				// Hence, we do not return at this point.
				// -- Madhukar Thakur (May 1, 1995)
			}
			else
				while (*a++);
			break;

		default:
			return SCL_corrupt;
		}
	}

	fb_assert(a == acl.end());

	return privilege;
}

