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

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


const int UIC_BASE	= 10;

using namespace Jrd;
using namespace Firebird;

DATABASE DB = FILENAME "ODS.RDB";

static bool check_number(const UCHAR*, USHORT);
static bool check_user_group(thread_db* tdbb, const UCHAR*, USHORT);
static bool check_string(const UCHAR*, const Firebird::MetaName&);
static SecurityClass::flags_t compute_access(thread_db* tdbb, const SecurityClass*,	SLONG, const Firebird::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 Firebird::MetaName& name,
	const Firebird::MetaName& r_name, const MetaName &invoker);


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(!num);
		return "<unknown object type>";
	}
} // anonymous namespace


static void raiseError(thread_db* tdbb, SecurityClass::flags_t mask, SLONG type, const Firebird::MetaName& name,
	const Firebird::MetaName& r_name, const Firebird::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();

	ERR_post(Arg::Gds(isc_no_priv) << Arg::Str(names->p_names_string) <<
			 Arg::Str(typeAsStr) << Arg::Str(fullName));
}


// Functional description
//	Check security class for desired permission.
//	userName a name of user in which context permissions will be checked.


static bool check_access(thread_db* tdbb,
					  const SecurityClass* s_class,
					  SLONG obj_type,
					  const Firebird::MetaName& obj_name,
					  SecurityClass::flags_t mask,
					  SLONG type,
					  bool recursive,
					  const Firebird::MetaName& name,
					  const Firebird::MetaName& r_name)
{
	SET_TDBB(tdbb);

	// RS: SCL_references must be checked for DDL operation of index creation
	// and we need to ignore TDBB_trusted_ddl flag in this case.
	// More general solution is to remove TDBB_trusted_dll flag since its purpose to
	// allow system table modification due DDL operations. It requires removing SCL_checks
	// from VIO_{store,erase,motify}. It's quite possible but not so trivial as at first look.
	if ((tdbb->tdbb_flags & TDBB_trusted_ddl) && (mask != SCL_references))
		return true;

	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);
		ERR_post(status);
	}

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

	// 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 true;

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

	if (obj_name.hasData())
	{
		SecurityClass::flags_t priv = 0;
		if (!s_class->getPrivileges(obj_name, obj_type, priv))
		{
			priv = compute_access(tdbb, s_class, obj_type, &obj_name);
			s_class->setPrivileges(obj_name, obj_type, priv);
		}
		if (priv & mask)
			return true;
	}

	// 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 true;
	}

	return false;
}


void SCL_check_access(thread_db* tdbb,
					  const SecurityClass* s_class,
					  const SecurityClass* view_s_class,
					  SLONG obj_type,
					  const Firebird::MetaName& obj_name,
					  SecurityClass::flags_t mask,
					  SLONG type,
					  bool recursive,
					  const Firebird::MetaName& name,
					  const Firebird::MetaName& r_name)
{
	if (check_access(tdbb, s_class, obj_type, obj_name, mask, type, recursive, name, r_name))
		return;
	if (view_s_class && check_access(tdbb, view_s_class, obj_type, obj_name, mask, type, recursive, name, r_name))
		return;

	raiseError(tdbb, mask, type, name, r_name, s_class->sclClassUser.second);
}


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);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	// Allow the locksmith any access to database
	if ((tdbb->tdbb_flags & TDBB_trusted_ddl) || attachment->locksmith())
		return;

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

	if (!(obj_mask & SCL_create))
		ERR_post(Arg::Gds(isc_dyn_no_priv));
}


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

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

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

	SCL_check_access(tdbb, s_class, NULL, 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, NULL, 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())
		return;

	if (mask == SCL_drop && attachment->locksmith())
		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, NULL, 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

	SCL_check_access(tdbb, s_class, NULL, 0, name, mask, SCL_object_exception, false, name);
	return found;
}


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

	SCL_check_access(tdbb, s_class, NULL, 0, name, mask, SCL_object_generator, false, name);
	return found;
}


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

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

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

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

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

	AutoRequest request;
	int systemFlag = 0;

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

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

	if (systemFlag == 1 && !attachment->isRWGbak())
	{
		// Someone is going to reference system table in FK
		// Usually it's not good idea
		raiseError(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, NULL, 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, NULL, 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 Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
		dsc_name->dsc_length);

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

	const SecurityClass* s_class = NULL;
	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

	SCL_check_access(tdbb, s_class, NULL, id_package, name, mask, SCL_object_package, false, name);
	return found;
}


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

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

	const SecurityClass* s_class = NULL;
	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

	SCL_check_access(tdbb, s_class, NULL, id_procedure, name, mask, SCL_object_procedure, false, name);
	return found;
}


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

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

	const SecurityClass* s_class = NULL;
	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

	SCL_check_access(tdbb, s_class, NULL, id_function, name, mask, SCL_object_function, false, name);
	return found;
}


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, NULL, id_filter, name, mask, SCL_object_filter, false, name);
}


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

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

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

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

	FOR(REQUEST_HANDLE request) REL IN RDB$RELATIONS
		WITH REL.RDB$RELATION_NAME EQ name.c_str()
	{
		if (protectSys && REL.RDB$SYSTEM_FLAG == 1 && !attachment->isRWGbak())
		{
			// Someone is going to modify system table layout
			// Usually it's not good idea
			raiseError(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, NULL, 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 Firebird::MetaName name(reinterpret_cast<TEXT*>(dsc_name->dsc_address),
								  dsc_name->dsc_length);

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

	const SecurityClass* s_class = NULL;
	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

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

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

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

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

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

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

SecurityClass* SCL_get_class(thread_db* tdbb, const TEXT* par_string, const jrd_rel* view)
{
/**************************************
 *
 *	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;

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

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

	Firebird::MetaName userName;
	if (view && (view->rel_flags & REL_sql_relation))
		userName = view->rel_owner_name;
	else
	{
		const UserId* effectiveUserId = attachment->att_user;
		if (effectiveUserId)
			userName = effectiveUserId->usr_user_name;
	}

	Firebird::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, view ? id_view : 0, view ? &view->rel_name : 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;
}


Jrd::SecurityClass* SCL_get_class_for_view(Jrd::thread_db* tdbb, SLONG view_id, const TEXT* par_string)
{
	if (!view_id)
		return NULL;

	const jrd_rel* view = MET_lookup_relation_id(tdbb, view_id, false);
	if (!view)
		return NULL;

	return SCL_get_class(tdbb, par_string, view);
}


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.usr_user_name.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;
}


bool SCL_admin_role(thread_db* tdbb, const TEXT* sql_role)
{
/**************************************
 *
 *	S C L _ a d m i n _ r o l e
 *
 **************************************
 *
 * Functional description
 *	Check is sql_role is an admin role.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	bool adminRole = false;

	AutoCacheRequest request(tdbb, irq_is_admin_role, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request) R IN RDB$ROLES
		WITH R.RDB$ROLE_NAME EQ sql_role
		 AND R.RDB$SYSTEM_FLAG != 0
	{
		adminRole = true;
	}
	END_FOR

	return adminRole;
}


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.
 *
 *	Finally fills UserId information
 *	(role, flags, etc.).
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	const TEXT* sql_role = tempId.usr_sql_role_name.nullStr();

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

	if (tempId.usr_user_name.hasData())
	{
		if (!create)
		{
			Firebird::string loginName(tempId.usr_user_name);
			const TEXT* login_name = loginName.c_str();

			AutoCacheRequest request(tdbb, irq_get_role_name, IRQ_REQUESTS);

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

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

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

	if (!sql_role)
		sql_role = tempId.usr_trusted_role.nullStr();

	MetaName role_name(sql_role ? sql_role : NULL_ROLE);

	MemoryPool& pool = *attachment->att_pool;
	UserId* const user = FB_NEW_POOL(pool) UserId(pool, tempId);
	user->usr_sql_role_name = role_name.c_str();
	user->usr_flags |= USR_newrole;
	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
		}

		if (dbb->dbb_owner == user->usr_user_name)
			user->usr_flags |= USR_owner;

		if (sql_role && SCL_admin_role(tdbb, role_name.c_str()))
			user->usr_flags |= USR_dba;
	}
	else
	{
		dbb->dbb_owner = user->usr_user_name;
		user->usr_flags |= USR_owner;

		user->usr_granted_role = "RDB$ADMIN";
		user->usr_flags &= ~USR_newrole;
	}
}


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

	if (usr_sql_role_name != usr_trusted_role)
	{
		AutoCacheRequest request(tdbb, irq_get_role_mem, IRQ_REQUESTS);
		usr_granted_role = "";

		FOR(REQUEST_HANDLE request) U IN RDB$USER_PRIVILEGES WITH
			(U.RDB$USER EQ usr_user_name.c_str() OR
			 U.RDB$USER EQ "PUBLIC") AND
				U.RDB$USER_TYPE EQ obj_user AND
				U.RDB$RELATION_NAME EQ usr_sql_role_name.c_str() AND
				U.RDB$OBJECT_TYPE EQ obj_sql_role AND
				U.RDB$PRIVILEGE EQ "M"
		{
			if (!U.RDB$USER.NULL)
				usr_granted_role = usr_sql_role_name;
		}
		END_FOR
	}
	else
		usr_granted_role = usr_sql_role_name;

	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;

	Firebird::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 Firebird::MetaName& string)
{
/**************************************
 *
 *	c h e c k _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Check a string against and acl string.  If they don't match,
 *	return true.
 *
 **************************************/
	fb_assert(acl);

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

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

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

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

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

	const size_t length = *acl++;
	const TEXT* const ptr = (TEXT*) acl;
	str.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())
		flags |= (SCL_ACCESS_ANY | SCL_SELECT_ANY | SCL_MODIFY_ANY);

	return flags;
}

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

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

	SecurityClass::flags_t privileges = 0;
	SecurityClass::flags_t sysPriv = SCL_exists | 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 emptyUser;
	UserId* user = &emptyUser;
	if (userName.hasData())
		user = attachment->getUserId(userName.c_str());

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

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

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

	while ( (c = *a++) )
	{
		switch (c)
		{
		case ACL_id_list:
			hit = true;
			while ( (c = *a++) )
			{
				switch (c)
				{
				case id_person:
					if (!(p = user->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:
				{
					Firebird::string 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 || (!obj_name) || 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_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 Jrd::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);
}

