/*
 *	PROGRAM:	JRD Access Method
 *	MODULE:		scl.epp
 *	DESCRIPTION:	Security class handler
 *
 * The contents of this file are subject to the Interbase Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy
 * of the License at http://www.Inprise.com/IPL.html
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code was created by Inprise Corporation
 * and its predecessors. Portions created by Inprise Corporation are
 * Copyright (C) Inprise Corporation.
 *
 * All Rights Reserved.
 * Contributor(s): ______________________________________.
 * 2001.6.12 Claudio Valderrama: the role should be wiped out if invalid.
 * 2001.8.12 Claudio Valderrama: Squash security bug when processing
 *           identifiers with embedded blanks: check_procedure, check_relation
 *           and check_string, the latter being called from many places.
 *
 */

#include "firebird.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "../jrd/jrd.h"
#include "../jrd/ods.h"
#include "../jrd/scl.h"
#include "../jrd/acl.h"
#include "../jrd/blb.h"
#include "../jrd/irq.h"
#include "../jrd/obj.h"
#include "../jrd/req.h"
#include "../jrd/tra.h"
#include "../common/gdsassert.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/ini_proto.h"
#include "../yvalve/gds_proto.h"
#include "../common/isc_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/grant_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/constants.h"
#include "fb_exception.h"
#include "../common/utils_proto.h"
#include "../common/classes/array.h"
#include "../common/config/config.h"
#include "../common/os/os_utils.h"
#include "../common/classes/ClumpletWriter.h"
#include "../jrd/PreparedStatement.h"
#include "../jrd/ResultSet.h"


const int UIC_BASE	= 10;

using namespace Jrd;
using namespace Firebird;

DATABASE DB = FILENAME "ODS.RDB";

static bool check_number(const UCHAR*, USHORT);
static bool check_user_group(thread_db* tdbb, const UCHAR*, USHORT);
static bool check_string(const UCHAR*, const MetaName&);
static SecurityClass::flags_t compute_access(thread_db* tdbb, const SecurityClass*,	SLONG, const MetaName&);
static SecurityClass::flags_t get_sys_privileges(thread_db* tdbb);
static SecurityClass::flags_t walk_acl(thread_db* tdbb, const Acl&, const MetaName&,
	SLONG, const MetaName&);
static void raiseError(thread_db* tdbb, SecurityClass::flags_t mask, SLONG type, const MetaName& name,
	const MetaName& r_name, const MetaName &invoker);
static bool check_object(thread_db* tdbb, bool found, const SecurityClass* s_class, SLONG obj_type,
	const MetaName& obj_name, SecurityClass::flags_t mask, SLONG type, const MetaName& name);


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

	const P_NAMES p_names[] =
	{
		{ SCL_alter, priv_alter, "ALTER" },
		{ SCL_control, priv_control, "CONTROL" },
		{ SCL_drop, priv_drop, "DROP" },
		{ SCL_insert, priv_insert, "INSERT" },
		{ SCL_update, priv_update, "UPDATE" },
		{ SCL_delete, priv_delete, "DELETE" },
		{ SCL_select, priv_select, "SELECT" },
		{ SCL_references, priv_references, "REFERENCES" },
		{ SCL_execute, priv_execute, "EXECUTE" },
		{ SCL_usage, priv_usage, "USAGE" },
		{ SCL_create, priv_create, "CREATE" },
		{ 0, 0, "" }
	};


	// Database is never requested through CMP_post_access.
	const char* const object_str_database	= "DATABASE";
	//const char* const object_str_schema		= "SCHEMA";
	const char* const object_str_table		= "TABLE";
	const char* const object_str_package	= "PACKAGE";
	const char* const object_str_procedure	= "PROCEDURE";
	const char* const object_str_function	= "FUNCTION";
	const char* const object_str_column		= "COLUMN";
	const char* const object_str_charset	= "CHARACTER SET";
	const char* const object_str_collation	= "COLLATION";
	const char* const object_str_domain		= "DOMAIN";
	const char* const object_str_exception	= "EXCEPTION";
	const char* const object_str_generator	= "GENERATOR";
	const char* const object_str_view		= "VIEW";
	const char* const object_str_role		= "ROLE";
	const char* const object_str_filter		= "FILTER";


	struct SecObjectNamePriority
	{
		const char* name;
		SLONG num;
	};

	const SecObjectNamePriority priorities[] =
	{
		{object_str_database, SCL_object_database},
		//{object_str_schema, SCL_object_schema},
		{object_str_table, SCL_object_table},
		{object_str_package, SCL_object_package},
		{object_str_procedure, SCL_object_procedure},
		{object_str_function, SCL_object_function},
		{object_str_column, SCL_object_column},
		{object_str_charset, SCL_object_charset},
		{object_str_collation, SCL_object_collation},
		{object_str_domain, SCL_object_domain},
		{object_str_exception, SCL_object_exception},
		{object_str_generator, SCL_object_generator},
		{object_str_view, SCL_object_view},
		{object_str_role, SCL_object_role},
		{object_str_filter, SCL_object_filter},
		{"", 0}
	};

	/* Unused, may be needed
	SLONG accTypeStrToNum(const char* name)
	{
		for (const SecObjectNamePriority* p = priorities; p->num != 0; ++p)
		{
			if (strcmp(p->name, name) == 0)
				return p->num;
		}
		fb_assert(false);
		return 0;
	}
	*/

	const char* accTypeNumToStr(const SLONG num)
	{
		for (const SecObjectNamePriority* p = priorities; p->num != 0; ++p)
		{
			if (p->num == num)
				return p->name;
		}
		fb_assert(false);
		return "<unknown object type>";
	}
} // anonymous namespace


static void raiseError(thread_db* tdbb, SecurityClass::flags_t mask, SLONG type, const MetaName& name,
	const MetaName& r_name, const MetaName& invoker)
{
	const P_NAMES* names;
	for (names = p_names; names->p_names_priv; names++)
	{
		if (names->p_names_priv & mask)
			break;
	}

	const char* const typeAsStr = accTypeNumToStr(type);
	const Firebird::string fullName = r_name.hasData() ?
		r_name.c_str() + Firebird::string(".") + name.c_str() : name.c_str();

	Arg::StatusVector status;
	status << Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
			  Arg::Str(typeAsStr) <<
			  Arg::Str(fullName);
	if (invoker.hasData())
	{
		status << Arg::Gds(isc_effective_user) << Arg::Str(invoker);
	}
	ERR_post(status);

}



void SCL_check_access(thread_db* tdbb,
					  const SecurityClass* s_class,
					  SLONG obj_type,
					  const MetaName& obj_name,
					  SecurityClass::flags_t mask,
					  SLONG type,
					  bool recursive,
					  const MetaName& name,
					  const 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.
 *	userName a name of user in which context permissions will be checked.
 *
 **************************************/
	SET_TDBB(tdbb);

	// Allow the replicator any access to database, its permissions are already validated
	if (tdbb->tdbb_flags & TDBB_replicator)
		return;

	const MetaName& userName = s_class->sclClassUser.second;

	if (s_class && (s_class->scl_flags & SCL_corrupt))
	{
		Arg::StatusVector status;
		status << Arg::Gds(isc_no_priv) << Arg::Str("(ACL unrecognized)") <<
										  Arg::Str("security_class") <<
										  Arg::Str(s_class->sclClassUser.first);
		if (userName.hasData())
		{
			status << Arg::Gds(isc_effective_user) << Arg::Str(userName);
		}
		ERR_post(status);
	}

	// Make fast check first
	if (mask & get_sys_privileges(tdbb))
		return;

	// Check global DDL permissions with ANY option which allow user to make changes non owned objects
	if ((type > obj_last_non_ddl) && (mask & SCL_get_object_mask(type)))
		return;

	if (!s_class || (mask & s_class->scl_flags) )
		return;

	if (obj_name.hasData() && (compute_access(tdbb, s_class, obj_type, obj_name) & mask) )
	{
		return;
	}

	// Allow recursive procedure/function call

	if (recursive &&
		((type == SCL_object_procedure && obj_type == id_procedure) ||
		 (type == SCL_object_function && obj_type == id_function)) &&
		obj_name == name)
	{
		return;
	}

	raiseError(tdbb, mask, type, name, r_name, userName);
}


void SCL_check_create_access(thread_db* tdbb, int type)
{
/**************************************
 *
 *	S C L _ c h e c k _ c r e a t e _ a c c e s s
 *
 **************************************
 *
 * Functional description
 *	Check create access on a database object (DDL access)
 *
 **************************************/
	SET_TDBB(tdbb);

	// Allow the replicator any access to database, its permissions are already validated
	if (tdbb->tdbb_flags & TDBB_replicator)
		return;

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

	// Allow the locksmith any access to database
	if (attachment->locksmith(tdbb, SystemPrivilege::MODIFY_ANY_OBJECT_IN_DATABASE))
		return;

	const SecurityClass::flags_t obj_mask = SCL_get_object_mask(type);

	if (!(obj_mask & SCL_create))
	{
		const char* name = accTypeNumToStr(type);
		ERR_post(Arg::Gds(isc_dyn_no_create_priv) << name);
	}
}


void SCL_check_charset(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ c h a r s e t
 *
 **************************************
 *
 * Functional description
 *	Given a character set name, check for a set of privileges.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const SecurityClass* s_class = NULL;
	AutoCacheRequest request(tdbb, irq_cs_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		CS IN RDB$CHARACTER_SETS
		WITH CS.RDB$CHARACTER_SET_NAME EQ name.c_str()
	{
		if (!CS.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, CS.RDB$SECURITY_CLASS);
	}
	END_FOR

	SCL_check_access(tdbb, s_class, 0, name, mask, SCL_object_charset, false, name);
}


void SCL_check_collation(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ c o l l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Given a collation name, check for a set of privileges.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const SecurityClass* s_class = NULL;
	AutoCacheRequest request(tdbb, irq_coll_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		COLL IN RDB$COLLATIONS
		WITH COLL.RDB$COLLATION_NAME EQ name.c_str()
	{
		if (!COLL.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, COLL.RDB$SECURITY_CLASS);
	}
	END_FOR

	SCL_check_access(tdbb, s_class, 0, name, mask, SCL_object_collation, false, name);
}


void SCL_check_database(thread_db* tdbb, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ d a t a b a s e
 *
 **************************************
 *
 * Functional description
 *	Check for a set of privileges of current database.
 *
 **************************************/
	SET_TDBB(tdbb);

	Jrd::Attachment* const attachment = tdbb->getAttachment();
	const SecurityClass* const att_class = attachment->att_security_class;
	if (att_class && (att_class->scl_flags & mask))
		return;

	if (mask == SCL_alter && attachment->locksmith(tdbb, USE_NBACKUP_UTILITY))
		return;

	if (mask == SCL_drop && attachment->locksmith(tdbb, DROP_DATABASE))
		return;

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

	ERR_post(Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
									  Arg::Str(object_str_database) <<
									  Arg::Str(""));
}


void SCL_check_domain(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ d o m a i n
 *
 **************************************
 *
 * Functional description
 *	Given a domain name, check for a set of privileges.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const SecurityClass* s_class = NULL;
	AutoCacheRequest request(tdbb, irq_gfld_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		FLD IN RDB$FIELDS
		WITH FLD.RDB$FIELD_NAME EQ name.c_str()
	{
		if (!FLD.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, FLD.RDB$SECURITY_CLASS);
	}
	END_FOR

	SCL_check_access(tdbb, s_class, 0, name, mask, SCL_object_domain, false, name);
}


bool SCL_check_exception(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ e x c e p t i o n
 *
 **************************************
 *
 * Functional description
 *	Given an exception name, check for a set of privileges.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const SecurityClass* s_class = NULL;
	bool found = false;
	AutoCacheRequest request(tdbb, irq_exc_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		XCP IN RDB$EXCEPTIONS
		WITH XCP.RDB$EXCEPTION_NAME EQ name.c_str()
	{
		if (!XCP.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, XCP.RDB$SECURITY_CLASS);
		found = true;
	}
	END_FOR

	return check_object(tdbb, found, s_class, 0, name, mask, SCL_object_exception, name);
}


bool SCL_check_generator(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ g e n e r a t o r
 *
 **************************************
 *
 * Functional description
 *	Given a generator name, check for a set of privileges.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const SecurityClass* s_class = NULL;
	bool found = false;
	AutoCacheRequest request(tdbb, irq_gen_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		GEN IN RDB$GENERATORS
		WITH GEN.RDB$GENERATOR_NAME EQ name.c_str()
	{
		if (!GEN.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, GEN.RDB$SECURITY_CLASS);
		found = true;
	}
	END_FOR

	return check_object(tdbb, found, s_class, 0, name, mask, SCL_object_generator, name);
}


void SCL_check_index(thread_db* tdbb, const MetaName& index_name, UCHAR index_id,
	SecurityClass::flags_t mask)
{
/******************************************************
 *
 *	S C L _ c h e c k _ i n d e x
 *
 ******************************************************
 *
 * Functional description
 *	Given a index name (as a TEXT), check for a
 *      set of privileges on the table that the index is on and
 *      on the fields involved in that index.
 *   CVC: Allow the same function to use the zero-based index id, too.
 *      The idx.idx_id value is zero based but system tables use
 *      index id's being one based, hence adjust the incoming value
 *      before calling this function. If you use index_id, index_name
 *      becomes relation_name since index ids are relative to tables.
 *
 *******************************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const SecurityClass* s_class = NULL;
	const SecurityClass* default_s_class = NULL;

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

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

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

	AutoRequest request;
	int systemFlag = 0;

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

    if (index_id < 1)
    {
        FOR(REQUEST_HANDLE request) IND IN RDB$INDICES
            CROSS REL IN RDB$RELATIONS
			OVER RDB$RELATION_NAME
			WITH IND.RDB$INDEX_NAME EQ index_name.c_str()
		{
            reln_name = REL.RDB$RELATION_NAME;
		    if (!REL.RDB$SECURITY_CLASS.NULL)
                s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
            if (!REL.RDB$DEFAULT_CLASS.NULL)
                default_s_class = SCL_get_class(tdbb, REL.RDB$DEFAULT_CLASS);
			systemFlag = REL.RDB$SYSTEM_FLAG;
		}
        END_FOR
    }
    else
    {
        idx_name_ptr = &aux_idx_name;
        FOR (REQUEST_HANDLE request) IND IN RDB$INDICES
            CROSS REL IN RDB$RELATIONS
            OVER RDB$RELATION_NAME
            WITH IND.RDB$RELATION_NAME EQ relation_name_ptr->c_str()
            AND IND.RDB$INDEX_ID EQ index_id
		{
            reln_name = REL.RDB$RELATION_NAME;
            aux_idx_name = IND.RDB$INDEX_NAME;
            if (!REL.RDB$SECURITY_CLASS.NULL)
                s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
            if (!REL.RDB$DEFAULT_CLASS.NULL)
                default_s_class = SCL_get_class(tdbb, REL.RDB$DEFAULT_CLASS);
			systemFlag = REL.RDB$SYSTEM_FLAG;
        }
        END_FOR
    }

	if (systemFlag == 1 && !attachment->isRWGbak())
	{
		// Someone is going to reference system table in FK
		// Usually it's not good idea
		raiseError(tdbb, mask, SCL_object_table, reln_name, "", NULL);
	}

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

	if (reln_name.isEmpty())
		return;

	SCL_check_access(tdbb, s_class, 0, NULL, mask, SCL_object_table, false, reln_name);

	request.reset();

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

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

	FOR(REQUEST_HANDLE request) ISEG IN RDB$INDEX_SEGMENTS
		CROSS RF IN RDB$RELATION_FIELDS
			OVER RDB$FIELD_NAME
			WITH RF.RDB$RELATION_NAME EQ reln_name.c_str()
			AND ISEG.RDB$INDEX_NAME EQ idx_name_ptr->c_str()
	{
		s_class = (!RF.RDB$SECURITY_CLASS.NULL) ?
			SCL_get_class(tdbb, RF.RDB$SECURITY_CLASS) : default_s_class;
		SCL_check_access(tdbb, s_class, 0, NULL, mask,
						 SCL_object_column, false, RF.RDB$FIELD_NAME, reln_name);
	}
	END_FOR
}


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

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

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

	const SecurityClass* s_class = NULL;
	bool found = false;
	AutoCacheRequest request(tdbb, irq_pkg_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		PKG IN RDB$PACKAGES
		WITH PKG.RDB$PACKAGE_NAME EQ name.c_str()
	{
		if (!PKG.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, PKG.RDB$SECURITY_CLASS);
		found = true;
	}
	END_FOR

	return check_object(tdbb, found, s_class, id_package, name, mask, SCL_object_package, name);
}


bool 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 MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
							dsc_name->dsc_length);

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

	const SecurityClass* s_class = NULL;
	bool found = false;
	AutoCacheRequest request(tdbb, irq_p_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		SPROC IN RDB$PROCEDURES
		WITH SPROC.RDB$PROCEDURE_NAME EQ name.c_str() AND
			 SPROC.RDB$PACKAGE_NAME MISSING
	{
		if (!SPROC.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, SPROC.RDB$SECURITY_CLASS);
		found = true;
	}
	END_FOR

	return check_object(tdbb, found, s_class, id_procedure, name, mask, SCL_object_procedure, name);
}


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

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

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

	const SecurityClass* s_class = NULL;
	bool found = false;
	AutoCacheRequest request(tdbb, irq_f_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		SFUN IN RDB$FUNCTIONS
		WITH SFUN.RDB$FUNCTION_NAME EQ name.c_str() AND
			 SFUN.RDB$PACKAGE_NAME MISSING
	{
		if (!SFUN.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, SFUN.RDB$SECURITY_CLASS);
		found = true;
	}
	END_FOR

	return check_object(tdbb, found, s_class, id_function, name, mask, SCL_object_function, name);
}


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

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

	const SecurityClass* s_class = NULL;
	AutoCacheRequest request(tdbb, irq_f_security, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		F IN RDB$FILTERS
		WITH F.RDB$FUNCTION_NAME EQ name.c_str()
	{
		if (!F.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, F.RDB$SECURITY_CLASS);
	}
	END_FOR

	SCL_check_access(tdbb, s_class, id_filter, name, mask, SCL_object_filter, false, name);
}


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

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

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

	const SecurityClass* s_class = NULL;
	AutoCacheRequest request(tdbb, irq_v_security_r, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS
		WITH REL.RDB$RELATION_NAME EQ name.c_str()
	{
		if (protectSys && REL.RDB$SYSTEM_FLAG == 1 && !attachment->isRWGbak())
		{
			// Someone is going to modify system table layout
			// Usually it's not good idea
			raiseError(tdbb, mask, SCL_object_table, name, "", NULL);
		}

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

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

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

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

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

	const SecurityClass* s_class = NULL;
	bool found = false;
	AutoCacheRequest request(tdbb, irq_v_security_v, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS
		WITH REL.RDB$RELATION_NAME EQ name.c_str()
	{
		if (!REL.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, REL.RDB$SECURITY_CLASS);
		found = true;
	}
	END_FOR

	return check_object(tdbb, found, s_class, 0, NULL, mask, SCL_object_view, name);
}

void SCL_check_role(thread_db* tdbb, const MetaName& name, SecurityClass::flags_t mask)
{
/**************************************
 *
 *	S C L _ c h e c k _ r o l e
 *
 **************************************
 *
 * Functional description
 *	Given a role name, check for a set of privileges.
 *
 **************************************/
	SET_TDBB(tdbb);

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

	const SecurityClass* s_class = NULL;
	AutoCacheRequest request(tdbb, irq_v_security_o, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) R IN RDB$ROLES
		WITH R.RDB$ROLE_NAME EQ name.c_str()
	{
		if (!R.RDB$SECURITY_CLASS.NULL)
			s_class = SCL_get_class(tdbb, R.RDB$SECURITY_CLASS);
	}
	END_FOR

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

SecurityClass* SCL_get_class(thread_db* tdbb, const TEXT* par_string)
{
/**************************************
 *
 *	S C L _ g e t _ c l a s s
 *
 **************************************
 *
 * Functional description
 *	Look up security class of the effective user first in memory, then in database.
 *	If we don't find it, just return NULL. If we do, return a security class block.
 *
 **************************************/
	SET_TDBB(tdbb);

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

	if (!par_string)
		return NULL;

	MetaName string(par_string);
	if (string.isEmpty())
		return NULL;

	Jrd::Attachment* const attachment = tdbb->getAttachment();
	const MetaString& userName = attachment->getEffectiveUserName();

	MetaNamePair key(string, userName);
	// Look for the class already known

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

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

	MemoryPool& pool = *attachment->att_pool;

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

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

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

	delete s_class;

	return NULL;
}


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

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

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

	return access & (SCL_select | SCL_drop | SCL_control |
					 SCL_insert | SCL_update |
					 SCL_delete | SCL_alter | SCL_references |
					 SCL_execute | SCL_usage);
}


bool SCL_role_granted(thread_db* tdbb, const UserId& usr, const TEXT* sql_role)
{
/**************************************
 *
 *	S C L _ r o l e _ g r a n t e d
 *
 **************************************
 *
 * Functional description
 *	Check is sql_role granted to the user.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	if (!strcmp(sql_role, NULL_ROLE))
	{
		return true;
	}

	bool found = false;

	AutoCacheRequest request(tdbb, irq_verify_role_name, IRQ_REQUESTS);

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

	FOR (REQUEST_HANDLE request) FIRST 1 RR IN RDB$ROLES
		CROSS UU IN RDB$USER_PRIVILEGES
		WITH RR.RDB$ROLE_NAME        EQ sql_role
		AND RR.RDB$ROLE_NAME         EQ UU.RDB$RELATION_NAME
		AND UU.RDB$OBJECT_TYPE       EQ obj_sql_role
		AND (UU.RDB$USER             EQ usr.getUserName().c_str()
			 OR UU.RDB$USER          EQ "PUBLIC")
		AND UU.RDB$USER_TYPE         EQ obj_user
		AND UU.RDB$PRIVILEGE         EQ "M"
	{
		if (!UU.RDB$USER.NULL)
			found = true;
	}
	END_FOR

	return found;
}


void UserId::findGrantedRoles(thread_db* tdbb) const
{
	try
	{
		SET_TDBB(tdbb);
		Jrd::Attachment* const attachment = tdbb->getAttachment();

		PreparedStatement::Builder sql;
		MetaName usr_get_role;
		string usr_get_priv;
		sql << "with recursive role_tree as ( "
			<< "   select rdb$relation_name as nm, 0 as ur from rdb$user_privileges "
			<< "       where rdb$privilege = 'M' and rdb$field_name = 'D'"
			<< "			and (rdb$user = " << usr_user_name << "	or rdb$user = 'PUBLIC')"
			<< "			and rdb$user_type = 8 "
			<< "   union all "
			<< "   select rdb$role_name as nm, 1 as ur from rdb$roles "
			<< "       where rdb$role_name = " << usr_sql_role_name
			<< "   union all "
			<< "   select p.rdb$relation_name as nm, t.ur from rdb$user_privileges p "
			<< "       join role_tree t on t.nm = p.rdb$user "
			<< "       where p.rdb$privilege = 'M' and (p.rdb$field_name = 'D' or t.ur = 1)) "
			<< "select " << sql("r.rdb$role_name, ",  usr_get_role)
			<< 	   sql("r.rdb$system_privileges ", usr_get_priv)
			<< "   from role_tree t join rdb$roles r on t.nm = r.rdb$role_name ";

		AutoPreparedStatement stmt(attachment->prepareStatement(tdbb, attachment->getSysTransaction(), sql));
		AutoResultSet rs(stmt->executeQuery(tdbb, attachment->getSysTransaction()));

		usr_granted_roles.clear();
		usr_privileges.clearAll();

		while (rs->fetch(tdbb))
		{
			if (!usr_granted_roles.exist(usr_get_role))	// SQL request can return duplicates
			{
				usr_granted_roles.add(usr_get_role);
				Privileges p;
				p.load(usr_get_priv.c_str());
				usr_privileges |= p;
			}
		}
	}
	catch (const Exception& e)
	{
		if (!(usr_flags & USR_sysdba))
			throw;

		e.stuffException(tdbb->tdbb_status_vector);
		if (!fb_utils::containsErrorCode(tdbb->tdbb_status_vector->getErrors(), isc_collation_not_installed))
			throw;

		usr_privileges.setAll();
		tdbb->tdbb_status_vector->init();
	}

	usr_flags &= ~(USR_newrole | USR_sysdba);
}


void UserId::setRoleTrusted()
{
	if (!usr_trusted_role.hasData())
		Arg::Gds(isc_miss_trusted_role).raise();
	setSqlRole(usr_trusted_role);
}


void UserId::sclInit(thread_db* tdbb, bool create)
{
/**************************************
 *
 *	S C L _ i n i t
 *
 **************************************
 *
 * Functional description
 *	Check database access control list.
 *
 *	Finally fills UserId information
 *	(role, flags, etc.).
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const TEXT* sql_role = getSqlRole().nullStr();

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

	if (getUserName().hasData() && !create)
	{
		const TEXT* login_name = getUserName().c_str();

		AutoCacheRequest request(tdbb, irq_get_role_name, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request) X IN RDB$ROLES
			WITH X.RDB$ROLE_NAME EQ login_name
		{
			ERR_post(Arg::Gds(isc_login_same_as_role_name) << Arg::Str(login_name));
		}
		END_FOR
	}

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

    if (!create && sql_role && *sql_role)
    {
        if (!SCL_role_granted(tdbb, *this, sql_role))
            sql_role = NULL;
    }

	if (!sql_role)
		sql_role = getTrustedRole().nullStr();

	MetaString role_name(sql_role ? sql_role : NULL_ROLE);

	MemoryPool& pool = *attachment->att_pool;
	UserId* const user = FB_NEW_POOL(pool) UserId(pool, *this);
	user->setSqlRole(role_name);
	user->usr_init_role = role_name;
	attachment->att_user = user;

	if (!create)
	{
		AutoCacheRequest request(tdbb, irq_get_att_class, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request) X IN RDB$DATABASE
		{
			if (!X.RDB$SECURITY_CLASS.NULL)
				attachment->att_security_class = SCL_get_class(tdbb, X.RDB$SECURITY_CLASS);
		}
		END_FOR

		if (dbb->dbb_owner.isEmpty())
		{
			AutoRequest request2;

			FOR(REQUEST_HANDLE request2)
				FIRST 1 REL IN RDB$RELATIONS
				WITH REL.RDB$RELATION_NAME EQ "RDB$DATABASE"
			{
	            if (!REL.RDB$OWNER_NAME.NULL)
					dbb->dbb_owner = REL.RDB$OWNER_NAME;
			}
			END_FOR
		}
	}
	else
	{
		dbb->dbb_owner = user->getUserName();
		user->usr_privileges.load(INI_owner_privileges().c_str());
		user->usr_granted_roles.clear();
		user->usr_granted_roles.add("RDB$ADMIN");

		user->usr_flags &= ~USR_newrole;
	}
}


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

	acl.push(ACL_end);
	acl.push(ACL_priv_list);

	bool rc = false;
	for (const P_NAMES* priv = p_names; priv->p_names_priv; priv++)
	{
		if (mask & priv->p_names_priv)
		{
			fb_assert(priv->p_names_acl <= MAX_UCHAR);
			acl.push(priv->p_names_acl);
			rc = true;
		}
	}

	acl.push(0);
	return rc;
}


void SCL_clear_classes(thread_db* tdbb, const TEXT* string)
{
/**************************************
 *
 *	S C L _ c l e a r _ c l a s s e s
 *
 **************************************
 *
 * Functional description
 *	Something changed with a security class, remove them from cache.
 *	We need to clear security classes for every user.
 *
 **************************************/
	SET_TDBB(tdbb);

	Attachment* attachment = tdbb->getAttachment();

	SecurityClassList* list = attachment->att_security_classes;
	if (!list)
		return;

	MetaNamePair key(string, NULL);
	if (!list->locate(Firebird::locGreatEqual, key))
		return;

	while (list->current()->sclClassUser.first == string)
	{
		delete list->current();
		if (!list->fastRemove())	// removing moves current item to the next one
			break;
	}
}


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

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

	delete list;
	list = NULL;
}


SecurityClass::flags_t SCL_get_object_mask(const int object_type)
{
/**************************************
 *
 *	S C L _ g e t _ o b j e c t _ m a s k
 *
 **************************************
 *
 *	Functional description
 *	Get a protection mask for database object.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	Database* dbb = tdbb->getDatabase();

	const TEXT* object_name = get_object_name(object_type);
	if (!*object_name)
		return 0;

	const Jrd::SecurityClass* s_class = SCL_get_class(tdbb, object_name);
	if (s_class)
		return s_class->scl_flags;

	return -1 & ~SCL_corrupt;
}


ULONG SCL_get_number(const UCHAR* acl)
{
/**************************************
 *
 *	g e t _ n u m b e r
 *
 **************************************
 *
 * Functional description
 *	Get value of acl numeric string.
 *
 **************************************/
	ULONG n = 0;
	USHORT l = *acl++;
	if (l)
	{
		do {
			n = n * UIC_BASE + *acl++ - '0';
		} while (--l);
	}

	return n;
}


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


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

	ULONG n = 0;

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

			// convert unix group name to unix group id
			n = os_utils::get_user_group_id(user_group_name.c_str());
		}
	}

	return (n != number);
}


static bool check_string(const UCHAR* acl, const MetaName& string)
{
/**************************************
 *
 *	c h e c k _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Check a string against and acl string.  If they don't match,
 *	return true.
 *
 **************************************/
	fb_assert(acl);

	const FB_SIZE_T length = *acl++;
	const TEXT* const ptr = (TEXT*) acl;

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

static void get_string(const UCHAR* acl, MetaName& string)
{
/**************************************
 *
 *	g e t _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Get a string from acl string.
 *
 **************************************/
	fb_assert(acl);

	const size_t length = *acl++;
	const TEXT* const ptr = (TEXT*) acl;
	string.assign(ptr, length);
}

static SecurityClass::flags_t get_sys_privileges(thread_db* tdbb)
{
/**************************************
 *
 *	g e t _ s y s _ p r i v i l e g e s
 *
 **************************************
 *
 * Functional description
 *	Returns access flags for current user's system-wide privileges
 *
 **************************************/
	const Jrd::Attachment* attachment = tdbb->getAttachment();
	if (!attachment)
		return 0;

	SecurityClass::flags_t flags = 0;

	if (attachment->locksmith(tdbb, ACCESS_ANY_OBJECT_IN_DATABASE))
		flags |= SCL_ACCESS_ANY;
	else if (attachment->locksmith(tdbb, SELECT_ANY_OBJECT_IN_DATABASE))
		flags |= SCL_SELECT_ANY;

	if (attachment->locksmith(tdbb, MODIFY_ANY_OBJECT_IN_DATABASE))
		flags |= SCL_MODIFY_ANY;

	return flags;
}

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

	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();
	jrd_tra* sysTransaction = attachment->getSysTransaction();

	SecurityClass::flags_t privileges = 0;
	SecurityClass::flags_t sysPriv = SCL_exists | get_sys_privileges(tdbb);

	AutoCacheRequest request(tdbb, irq_l_security, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) X IN RDB$SECURITY_CLASSES
		WITH X.RDB$SECURITY_CLASS EQ s_class->sclClassUser.first.c_str()
	{
		privileges |= sysPriv;
		blb* blob = blb::open(tdbb, sysTransaction, &X.RDB$ACL);
		UCHAR* buffer = acl.getBuffer(ACL_BLOB_BUFFER_SIZE);
		UCHAR* end = buffer;
		while (true)
		{
			end += blob->BLB_get_segment(tdbb, end, (USHORT) (acl.getCount() - (end - buffer)) );
			if (blob->blb_flags & BLB_eof)
				break;

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

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

		if (acl.getCount() > 0)
			privileges |= walk_acl(tdbb, acl, s_class->sclClassUser.second, obj_type, obj_name);
	}
	END_FOR

	return privileges;
}


static SecurityClass::flags_t walk_acl(thread_db* tdbb,
									   const Acl& acl,
									   const MetaName& userName,
									   SLONG obj_type,
									   const MetaName& obj_name)
{
/**************************************
 *
 *	w a l k _ a c l
 *
 **************************************
 *
 * Functional description
 *	Walk an access control list looking for a hit.  If a hit
 *	is found, return privileges.
 * userName a name of user which privileges must be computed. NULL is current user (INVOKER)
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

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

	UserId user;
	if (userName.hasData())
		user = *attachment->getUserId(userName);

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

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

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

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

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

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

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

				case id_sql_role:
				{
					MetaName role_name;
					get_string(a, role_name);
					if (!user.roleInUse(tdbb, role_name))
						hit = false;
					break;
				}

				case id_view:
				case id_package:
				case id_procedure:
				case id_trigger:
				case id_function:
					if (c != obj_type || check_string(a, obj_name))
						hit = false;
					break;

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

					hit = false;
					//if (!view)
					//	hit = false;
					break;

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

				case id_privilege:
					if (!user.locksmith(tdbb, SCL_get_number(a)))
						hit = false;
					break;

				case id_node:
					break;

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

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

					case priv_select:
						// Note that SELECT access must imply REFERENCES
						// access for upward compatibility of existing
						// security classes
						privilege |= SCL_select | SCL_references;
						break;

					case priv_insert:
						privilege |= SCL_insert;
						break;

					case priv_delete:
						privilege |= SCL_delete;
						break;

					case priv_references:
						privilege |= SCL_references;
						break;

					case priv_update:
						privilege |= SCL_update;
						break;

					case priv_drop:
						privilege |= SCL_drop;
						break;

					case priv_alter:
						privilege |= SCL_alter;
						break;

					case priv_execute:
						privilege |= SCL_execute;
						break;

					case priv_usage:
						privilege |= SCL_usage;
						break;

					case priv_write:
						// unused, but supported for backward compatibility
						privilege |= SCL_insert | SCL_update | SCL_delete;
						break;

					case priv_grant:
						// unused
						break;

					case priv_create:
						privilege |= SCL_create;
						break;

					default:
						return SCL_corrupt;
					}
				}

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

		default:
			return SCL_corrupt;
		}
	}

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

	return privilege;
}

void UserId::populateDpb(Firebird::ClumpletWriter& dpb, bool embeddedSupport)
{
	if (usr_auth_block.hasData())
		dpb.insertBytes(isc_dpb_auth_block, usr_auth_block.begin(), usr_auth_block.getCount());
	else if (embeddedSupport)
		dpb.insertString(isc_dpb_user_name, usr_user_name);

	if (usr_sql_role_name.hasData() && usr_sql_role_name != NULL_ROLE && !dpb.find(isc_dpb_sql_role_name))
		dpb.insertString(isc_dpb_sql_role_name, usr_sql_role_name);
}

void UserId::makeRoleName(Firebird::MetaString& role, const int dialect)
{
	if (role.isEmpty())
		return;

	switch (dialect)
	{
	case SQL_DIALECT_V5:
		// Invoke utility twice: first to strip quotes, next to uppercase if needed
		// For unquoted string nothing bad happens
		fb_utils::dpbItemUpper(role);
		{
			Firebird::string tmp(role);
			tmp.upper();
			role = tmp;
		}
		break;

	case SQL_DIALECT_V6_TRANSITION:
	case SQL_DIALECT_V6:
		fb_utils::dpbItemUpper(role);
		break;

	default:
		break;
	}
}

// get privilege bit by name
USHORT SCL_convert_privilege(thread_db* tdbb, jrd_tra* transaction, const Firebird::string& priv)
{
	static GlobalPtr<Mutex> privCacheMutex;
	static bool cacheFlag = false;
	typedef NonPooled<MetaString, USHORT> CachedPriv;
	static GlobalPtr<GenericMap<CachedPriv> > privCache;

	if (!cacheFlag)
	{
		MutexLockGuard g(privCacheMutex, FB_FUNCTION);

		if (!cacheFlag)
		{
			privCache->clear();

			AutoCacheRequest request(tdbb, irq_get_priv_bit, IRQ_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				T IN RDB$TYPES
				WITH T.RDB$FIELD_NAME EQ 'RDB$SYSTEM_PRIVILEGES'
			{
				privCache->put(T.RDB$TYPE_NAME, T.RDB$TYPE);
			}
			END_FOR

			cacheFlag = true;
		}
	}

	USHORT rc;
	if (!privCache->get(priv, rc))
		(Arg::Gds(isc_wrong_prvlg) << priv).raise();

	return rc;
}

// check object existence and access rights
static bool check_object(thread_db* tdbb,
						 bool found,
						 const SecurityClass* s_class,
						 SLONG obj_type,
						 const MetaName& obj_name,
						 SecurityClass::flags_t mask,
						 SLONG type,
						 const MetaName& name)
{
	if (s_class)
	{
		SCL_check_access(tdbb, s_class, obj_type, obj_name, mask, type, false, name);
		return true;
	}
	return found;
}

