8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-29 06:43:03 +01:00
firebird-mirror/src/jrd/Mapping.cpp

1359 lines
32 KiB
C++

/*
* PROGRAM: JRD access method
* MODULE: Mapping.cpp
* DESCRIPTION: Maps names in authentication block
*
* The contents of this file are subject to the Initial
* Developer's 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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
*
* Software distributed under the License is distributed AS IS,
* 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 Alex Peshkov
* for the Firebird Open Source RDBMS project.
*
* Copyright (c) 2014 Alex Peshkov <peshkoff at mail.ru>
* and all contributors signed below.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
*
*
*/
#include "firebird.h"
#include "firebird/Interface.h"
#include "../auth/SecureRemotePassword/Message.h"
#include "gen/iberror.h"
#include "../jrd/constants.h"
#include "../common/classes/ClumpletWriter.h"
#include "../common/classes/init.h"
#include "../common/classes/Hash.h"
#include "../common/classes/GenericMap.h"
#include "../common/classes/RefMutex.h"
#include "../common/classes/SyncObject.h"
#include "../common/classes/MetaName.h"
#include "../common/isc_s_proto.h"
#include "../common/isc_proto.h"
#include "../common/ThreadStart.h"
#include "../common/db_alias.h"
#include "../jrd/Mapping.h"
#include "../jrd/tra.h"
#include "../jrd/ini.h"
#include "../jrd/status.h"
#include "gen/ids.h"
#ifdef WIN_NT
#include <process.h>
#define getpid _getpid
#endif
#define MAP_DEBUG(A)
using namespace Firebird;
using namespace Jrd;
namespace {
const unsigned FLAG_DB = 1;
const unsigned FLAG_SEC = 2;
const unsigned FLAG_USER = 1;
const unsigned FLAG_ROLE = 2;
const char* NM_ROLE = "Role";
const char* NM_USER = "User";
const char* TYPE_SEEN = "Seen";
void check(const char* s, IStatus* st)
{
if (!(st->getState() & IStatus::STATE_ERRORS))
return;
Arg::StatusVector newStatus(st);
newStatus << Arg::Gds(isc_map_load) << s;
newStatus.raise();
}
class AuthWriter : public ClumpletWriter
{
public:
AuthWriter()
: ClumpletWriter(WideUnTagged, MAX_DPB_SIZE), sequence(0)
{ }
void append(AuthWriter& w)
{
internalAppend(w);
}
void append(const AuthReader::AuthBlock& b)
{
ClumpletReader r(WideUnTagged, b.begin(), b.getCount());
internalAppend(r);
}
void add(const AuthReader::Info& info)
{
ClumpletWriter to(WideUnTagged, MAX_DPB_SIZE);
add(to, AuthReader::AUTH_TYPE, info.type);
add(to, AuthReader::AUTH_NAME, info.name);
add(to, AuthReader::AUTH_PLUGIN, info.plugin);
add(to, AuthReader::AUTH_SECURE_DB, info.secDb);
add(to, AuthReader::AUTH_ORIG_PLUG, info.origPlug);
if (to.getBufferLength())
{
moveNext();
insertBytes(sequence++, to.getBuffer(), to.getBufferLength());
}
}
private:
void add(ClumpletWriter& to, const unsigned char tag, const NoCaseString& str)
{
if (str.hasData())
{
to.insertString(tag, str.c_str(), str.length());
}
}
void internalAppend(ClumpletReader& w)
{
while (!isEof())
{
moveNext();
}
for(w.rewind(); !w.isEof(); w.moveNext())
{
SingleClumplet sc = w.getClumplet();
sc.tag = sequence++;
insertClumplet(sc);
moveNext();
}
}
unsigned char sequence;
};
class Map;
typedef Hash<Map, DEFAULT_HASH_SIZE, Map, DefaultKeyValue<Map>, Map> MapHash;
class Map : public MapHash::Entry, public GlobalStorage
{
public:
static FB_SIZE_T hash(const Map& value, FB_SIZE_T hashSize)
{
NoCaseString key = value.makeHashKey();
return DefaultHash<Map>::hash(key.c_str(), key.length(), hashSize);
}
NoCaseString makeHashKey() const
{
NoCaseString key;
key += usng;
MAP_DEBUG(key += ':');
key += plugin;
MAP_DEBUG(key += ':');
key += db;
MAP_DEBUG(key += ':');
key += fromType;
MAP_DEBUG(key += ':');
key += from;
key.upper();
return key;
}
NoCaseString plugin, db, fromType, from, to;
bool toRole;
char usng;
Map(const char* aUsing, const char* aPlugin, const char* aDb,
const char* aFromType, const char* aFrom,
SSHORT aRole, const char* aTo)
: plugin(getPool()), db(getPool()), fromType(getPool()),
from(getPool()), to(getPool()), toRole(aRole ? true : false), usng(aUsing[0])
{
plugin = aPlugin;
db = aDb;
fromType = aFromType;
from = aFrom;
to = aTo;
trimAll();
}
explicit Map(AuthReader::Info& info) //type, name, plugin, secDb
: plugin(getPool()), db(getPool()),
fromType(getPool()), from(getPool()), to(getPool()),
toRole(false), usng(info.plugin.hasData() ? 'P' : 'M')
{
plugin = info.plugin.hasData() ? info.plugin.c_str() : "*";
db = info.secDb.hasData() ? info.secDb.c_str() : "*";
fromType = info.type;
from = info.name.hasData() ? info.name.c_str() : "*";
trimAll();
}
void trimAll()
{
plugin.rtrim();
db.rtrim();
fromType.rtrim();
from.rtrim();
to.rtrim();
}
virtual bool isEqual(const Map& k) const
{
return usng == k.usng &&
plugin == k.plugin &&
db == k.db &&
fromType == k.fromType &&
from == k.from ;
}
virtual Map* get()
{
return this;
}
};
class Cache : public MapHash, public GlobalStorage
{
public:
Cache(const NoCaseString& aliasDb, const NoCaseString& db)
: alias(getPool(), aliasDb), name(getPool(), db),
dataFlag(false), downFlag(false)
{
enableDuplicates();
}
void populate(IAttachment *att, bool isDown)
{
FbLocalStatus st;
if (dataFlag)
{
return;
}
if (!att)
{
dataFlag = true;
downFlag = isDown;
return;
}
MAP_DEBUG(fprintf(stderr, "Populate cache for %s\n", name.c_str()));
ITransaction* tra = NULL;
IResultSet* curs = NULL;
try
{
cleanup(eraseEntry);
ClumpletWriter readOnly(ClumpletWriter::Tpb, MAX_DPB_SIZE, isc_tpb_version1);
readOnly.insertTag(isc_tpb_read);
readOnly.insertTag(isc_tpb_wait);
tra = att->startTransaction(&st, readOnly.getBufferLength(), readOnly.getBuffer());
check("IAttachment::startTransaction", &st);
Message mMap;
Field<Text> usng(mMap, 1);
Field<Varying> plugin(mMap, MAX_SQL_IDENTIFIER_SIZE);
Field<Varying> db(mMap, MAX_SQL_IDENTIFIER_SIZE);
Field<Varying> fromType(mMap, MAX_SQL_IDENTIFIER_SIZE);
Field<Varying> from(mMap, 255);
Field<SSHORT> role(mMap);
Field<Varying> to(mMap, MAX_SQL_IDENTIFIER_SIZE);
curs = att->openCursor(&st, tra, 0,
"SELECT RDB$MAP_USING, RDB$MAP_PLUGIN, RDB$MAP_DB, RDB$MAP_FROM_TYPE, "
" RDB$MAP_FROM, RDB$MAP_TO_TYPE, RDB$MAP_TO "
"FROM RDB$AUTH_MAPPING",
3, NULL, NULL, mMap.getMetadata(), NULL, 0);
if (st->getState() & IStatus::STATE_ERRORS)
{
if (fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err))
{
// isc_dsql_relation_err when opening cursor - i.e. table RDB$AUTH_MAPPING
// is missing due to non-FB3 security DB
tra->release();
dataFlag = true;
return;
}
check("IAttachment::openCursor", &st);
}
while (curs->fetchNext(&st, mMap.getBuffer()) == IStatus::RESULT_OK)
{
const char* expandedDb = "*";
PathName target;
if (!db.null)
{
expandedDb = db;
MAP_DEBUG(fprintf(stderr, "non-expandedDb '%s'\n", expandedDb));
expandDatabaseName(expandedDb, target, NULL);
expandedDb = target.c_str();
MAP_DEBUG(fprintf(stderr, "expandedDb '%s'\n", expandedDb));
}
Map* map = FB_NEW Map(usng, plugin.null ? "*" : plugin, expandedDb,
fromType, from, role, to.null ? "*" : to);
MAP_DEBUG(fprintf(stderr, "Add = %s\n", map->makeHashKey().c_str()));
add(map);
}
check("IResultSet::fetchNext", &st);
curs->close(&st);
check("IResultSet::close", &st);
curs = NULL;
tra->rollback(&st);
check("ITransaction::rollback", &st);
tra = NULL;
dataFlag = true;
downFlag = false;
}
catch (const Exception&)
{
if (curs)
curs->release();
if (tra)
tra->release();
throw;
}
}
void map(bool flagWild, AuthReader::Info& info, AuthWriter& newBlock)
{
if (info.type == TYPE_SEEN)
return;
Map from(info);
if (from.from == "*")
Arg::Gds(isc_map_aster).raise();
if (!flagWild)
search(info, from, newBlock, from.from);
else
varUsing(info, from, newBlock);
}
void search(AuthReader::Info& info, const Map& from, AuthWriter& newBlock,
const NoCaseString& originalUserName)
{
MAP_DEBUG(fprintf(stderr, "Key = %s\n", from.makeHashKey().c_str()));
if (!dataFlag)
return;
for (Map* to = lookup(from); to; to = to->next(from))
{
MAP_DEBUG(fprintf(stderr, "Match!!\n"));
unsigned flagRolUsr = to->toRole ? FLAG_ROLE : FLAG_USER;
if (info.found & flagRolUsr)
continue;
if (info.current & flagRolUsr)
(Arg::Gds(isc_map_multi) << originalUserName).raise();
info.current |= flagRolUsr;
AuthReader::Info newInfo;
newInfo.type = to->toRole ? NM_ROLE : NM_USER;
newInfo.name = to->to == "*" ? originalUserName : to->to;
newInfo.secDb = this->name;
newInfo.origPlug = info.origPlug.hasData() ? info.origPlug : info.plugin;
newBlock.add(newInfo);
}
}
void varPlugin(AuthReader::Info& info, Map from, AuthWriter& newBlock)
{
varDb(info, from, newBlock);
if (from.plugin != "*")
{
from.plugin = "*";
varDb(info, from, newBlock);
}
}
void varDb(AuthReader::Info& info, Map from, AuthWriter& newBlock)
{
varFrom(info, from, newBlock);
if (from.db != "*")
{
from.db = "*";
varFrom(info, from, newBlock);
}
}
void varFrom(AuthReader::Info& info, Map from, AuthWriter& newBlock)
{
NoCaseString originalUserName = from.from;
search(info, from, newBlock, originalUserName);
from.from = "*";
search(info, from, newBlock, originalUserName);
}
void varUsing(AuthReader::Info& info, Map from, AuthWriter& newBlock)
{
if (from.usng == 'P')
{
varPlugin(info, from, newBlock);
from.usng = '*';
varPlugin(info, from, newBlock);
if (!info.secDb.hasData())
{
from.usng = 'S';
from.plugin = "*";
varDb(info, from, newBlock);
}
}
else if (from.usng == 'M')
{
varDb(info, from, newBlock);
from.usng = '*';
varDb(info, from, newBlock);
}
else
fb_assert(false);
}
bool map4(bool flagWild, unsigned flagSet, AuthReader& rdr, AuthReader::Info& info, AuthWriter& newBlock)
{
if (!flagSet)
{
AuthWriter workBlock;
for (rdr.rewind(); rdr.getInfo(info); rdr.moveNext())
{
map(flagWild, info, workBlock);
}
info.found |= info.current;
info.current = 0;
newBlock.append(workBlock);
}
unsigned mapMask = FLAG_USER | FLAG_ROLE;
return (info.found & mapMask) == mapMask;
}
static void eraseEntry(Map* m)
{
delete m;
}
void makeEmpty()
{
if (!dataFlag)
return;
dataFlag = false;
cleanup(eraseEntry);
}
public:
SyncObject syncObject;
NoCaseString alias, name;
bool dataFlag, downFlag;
};
typedef GenericMap<Pair<Left<NoCaseString, Cache*> > > CacheTree;
InitInstance<CacheTree> tree;
GlobalPtr<Mutex> treeMutex;
void setupIpc();
Cache* locate(const NoCaseString& target)
{
fb_assert(treeMutex->locked());
Cache* c;
return tree().get(target, c) ? c : NULL;
}
Cache* locate(const NoCaseString& alias, const NoCaseString& target)
{
fb_assert(treeMutex->locked());
Cache* c = locate(target);
if (!c)
{
c = FB_NEW Cache(alias, target);
*(tree().put(target)) = c;
setupIpc();
}
return c;
}
class Found
{
public:
enum What {FND_NOTHING, FND_SEC, FND_DB};
Found()
: found(FND_NOTHING)
{ }
void set(What find, const AuthReader::Info& val)
{
if (find == found && value != val.name)
Arg::Gds(isc_map_undefined).raise();
if (find > found)
{
found = find;
value = val.name;
if (val.plugin.hasData())
method = val.plugin;
else
method = "Mapped from " + val.origPlug;
}
}
NoCaseString value;
NoCaseString method;
What found;
};
void resetMap(const char* securityDb)
{
MutexLockGuard g(treeMutex, FB_FUNCTION);
Cache* cache = locate(securityDb);
if (!cache)
{
MAP_DEBUG(fprintf(stderr, "Cache not found for %s\n", securityDb));
return;
}
Sync sync(&cache->syncObject, FB_FUNCTION);
sync.lock(SYNC_EXCLUSIVE);
cache->makeEmpty();
MAP_DEBUG(fprintf(stderr, "Empty cache for %s\n", securityDb));
}
// ----------------------------------------------------
class MappingHeader : public Firebird::MemoryHeader
{
public:
SLONG currentProcess;
ULONG processes;
char databaseForReset[1024];
struct Process
{
event_t notifyEvent;
event_t callbackEvent;
SLONG id;
SLONG flags;
};
Process process[1];
static const ULONG FLAG_ACTIVE = 0x1;
static const ULONG FLAG_DELIVER = 0x2;
};
class MappingIpc FB_FINAL : public Firebird::IpcObject
{
static const USHORT MAPPING_VERSION = 1;
static const size_t DEFAULT_SIZE = 1024 * 1024;
public:
explicit MappingIpc(MemoryPool&)
: processId(getpid())
{ }
~MappingIpc()
{
if (!sharedMemory)
return;
Guard gShared(this);
MappingHeader* sMem = sharedMemory->getHeader();
startupSemaphore.tryEnter(5);
sMem->process[process].flags &= ~MappingHeader::FLAG_ACTIVE;
(void) // Ignore errors in cleanup
sharedMemory->eventPost(&sMem->process[process].notifyEvent);
cleanupSemaphore.tryEnter(5);
// Ignore errors in cleanup
sharedMemory->eventFini(&sMem->process[process].notifyEvent);
sharedMemory->eventFini(&sMem->process[process].callbackEvent);
if (sharedMemory->getHeader()->processes == 1)
sharedMemory->removeMapFile();
}
void clearMap(const char* dbName)
{
PathName target;
expandDatabaseName(dbName, target, NULL);
setup();
Guard gShared(this);
MappingHeader* sMem = sharedMemory->getHeader();
target.copyTo(sMem->databaseForReset, sizeof(sMem->databaseForReset));
// Set currentProcess
sMem->currentProcess = -1;
for (unsigned n = 0; n < sMem->processes; ++n)
{
MappingHeader::Process* p = &sMem->process[n];
if (!(p->flags & MappingHeader::FLAG_ACTIVE))
continue;
if (p->id == processId)
{
sMem->currentProcess = n;
break;
}
}
if (sMem->currentProcess < 0)
{
// did not find current process
// better ignore delivery than fail in it
gds__log("MappingIpc::clearMap() failed to find current process %d in shared memory", processId);
return;
}
MappingHeader::Process* current = &sMem->process[sMem->currentProcess];
// Deliver
for (unsigned n = 0; n < sMem->processes; ++n)
{
MappingHeader::Process* p = &sMem->process[n];
if (!(p->flags & MappingHeader::FLAG_ACTIVE))
continue;
if (p->id == processId)
{
MAP_DEBUG(fprintf(stderr, "Internal resetMap(%s)\n", sMem->databaseForReset));
resetMap(sMem->databaseForReset);
continue;
}
SLONG value = sharedMemory->eventClear(&current->callbackEvent);
p->flags |= MappingHeader::FLAG_DELIVER;
if (sharedMemory->eventPost(&p->notifyEvent) != FB_SUCCESS)
{
(Arg::Gds(isc_random) << "Error posting notifyEvent in mapping shared memory").raise();
}
while (sharedMemory->eventWait(&current->callbackEvent, value, 10000) != FB_SUCCESS)
{
if (!ISC_check_process_existence(p->id))
{
p->flags &= ~MappingHeader::FLAG_ACTIVE;
sharedMemory->eventFini(&sMem->process[process].notifyEvent);
sharedMemory->eventFini(&sMem->process[process].callbackEvent);
break;
}
}
MAP_DEBUG(fprintf(stderr, "Notified pid %d about reset map %s\n", p->id, sMem->databaseForReset));
}
}
void setup()
{
if (sharedMemory)
return;
MutexLockGuard gLocal(initMutex, FB_FUNCTION);
if (sharedMemory)
return;
Arg::StatusVector statusVector;
try
{
sharedMemory.reset(FB_NEW_POOL(*getDefaultMemoryPool())
SharedMemory<MappingHeader>("fb_user_mapping", DEFAULT_SIZE, this));
}
catch (const Exception& ex)
{
iscLogException("MappingIpc: Cannot initialize the shared memory region", ex);
throw;
}
fb_assert(sharedMemory->getHeader()->mhb_header_version == MemoryHeader::HEADER_VERSION);
fb_assert(sharedMemory->getHeader()->mhb_version == MAPPING_VERSION);
Guard gShared(this);
MappingHeader* sMem = sharedMemory->getHeader();
for (process = 0; process < sMem->processes; ++process)
{
if (!(sMem->process[process].flags & MappingHeader::FLAG_ACTIVE))
break;
if (!ISC_check_process_existence(processId))
{
sharedMemory->eventFini(&sMem->process[process].notifyEvent);
sharedMemory->eventFini(&sMem->process[process].callbackEvent);
break;
}
}
if (process >= sMem->processes)
{
sMem->processes++;
if (((U_IPTR) &sMem->process[sMem->processes]) - ((U_IPTR) sMem) > DEFAULT_SIZE)
{
sMem->processes--;
(Arg::Gds(isc_random) << "Global mapping memory overflow").raise();
}
}
sMem->process[process].id = processId;
sMem->process[process].flags = MappingHeader::FLAG_ACTIVE;
if (sharedMemory->eventInit(&sMem->process[process].notifyEvent) != FB_SUCCESS)
{
(Arg::Gds(isc_random) << "Error initializing notifyEvent in mapping shared memory").raise();
}
if (sharedMemory->eventInit(&sMem->process[process].callbackEvent) != FB_SUCCESS)
{
(Arg::Gds(isc_random) << "Error initializing callbackEvent in mapping shared memory").raise();
}
try
{
Thread::start(clearDelivery, this, THREAD_high);
}
catch (const Exception&)
{
sMem->process[process].flags &= ~MappingHeader::FLAG_ACTIVE;
throw;
}
}
private:
void clearDeliveryThread()
{
bool startup = true;
try
{
MappingHeader::Process* p = &sharedMemory->getHeader()->process[process];
while (p->flags & MappingHeader::FLAG_ACTIVE)
{
SLONG value = sharedMemory->eventClear(&p->notifyEvent);
if (p->flags & MappingHeader::FLAG_DELIVER)
{
resetMap(sharedMemory->getHeader()->databaseForReset);
MappingHeader* sMem = sharedMemory->getHeader();
MappingHeader::Process* cur = &sMem->process[sMem->currentProcess];
if (sharedMemory->eventPost(&cur->callbackEvent) != FB_SUCCESS)
{
(Arg::Gds(isc_random) << "Error posting callbackEvent in mapping shared memory").raise();
}
p->flags &= ~MappingHeader::FLAG_DELIVER;
}
if (startup)
{
startup = false;
startupSemaphore.release();
}
if (sharedMemory->eventWait(&p->notifyEvent, value, 0) != FB_SUCCESS)
{
(Arg::Gds(isc_random) << "Error waiting for notifyEvent in mapping shared memory").raise();
}
}
if (startup)
startupSemaphore.release();
cleanupSemaphore.release();
}
catch (const Exception& ex)
{
iscLogException("Fatal error in clearDeliveryThread", ex);
fb_utils::logAndDie("Fatal error in clearDeliveryThread");
}
}
// implement pure virtual functions
bool initialize(SharedMemoryBase* sm, bool initFlag)
{
if (initFlag)
{
MappingHeader* header = reinterpret_cast<MappingHeader*>(sm->sh_mem_header);
// Initialize the shared data header
header->init(SharedMemoryBase::SRAM_MAPPING_RESET, MAPPING_VERSION);
header->processes = 0;
header->currentProcess = -1;
}
return true;
}
void mutexBug(int osErrorCode, const char* text)
{
iscLogStatus("Error when working with user mapping shared memory",
(Arg::Gds(isc_sys_request) << text << Arg::OsError(osErrorCode)).value());
}
// copying is prohibited
MappingIpc(const MappingIpc&);
MappingIpc& operator =(const MappingIpc&);
class Guard;
friend class Guard;
class Guard
{
public:
explicit Guard(MappingIpc* ptr)
: data(ptr)
{
data->sharedMemory->mutexLock();
}
~Guard()
{
data->sharedMemory->mutexUnlock();
}
private:
Guard(const Guard&);
Guard& operator=(const Guard&);
MappingIpc* const data;
};
static THREAD_ENTRY_DECLARE clearDelivery(THREAD_ENTRY_PARAM par)
{
MappingIpc* m = (MappingIpc*)par;
m->clearDeliveryThread();
return 0;
}
AutoPtr<SharedMemory<MappingHeader> > sharedMemory;
Mutex initMutex;
const SLONG processId;
unsigned process;
Semaphore startupSemaphore;
Semaphore cleanupSemaphore;
};
GlobalPtr<MappingIpc, InstanceControl::PRIORITY_DELETE_FIRST> mappingIpc;
void setupIpc()
{
mappingIpc->setup();
}
} // anonymous namespace
namespace Jrd {
bool mapUser(string& name, string& trusted_role, Firebird::string* auth_method,
AuthReader::AuthBlock* newAuthBlock, const AuthReader::AuthBlock& authBlock,
const char* alias, const char* db, const char* securityAlias,
ICryptKeyCallback* cryptCb)
{
AuthReader::Info info;
if (!securityAlias)
{
// We are in the error handler - perform minimum processing
trusted_role = "";
name = "<Unknown>";
for (AuthReader rdr(authBlock); rdr.getInfo(info); rdr.moveNext())
{
if (info.type == NM_USER && info.name.hasData())
{
name = info.name.ToString();
break;
}
}
return false;
}
// expand security database name (db is expected to be expanded, alias - original)
PathName secExpanded;
expandDatabaseName(securityAlias, secExpanded, NULL);
const char* securityDb = secExpanded.c_str();
bool secDown = false;
bool dbDown = false;
// Create new writer
AuthWriter newBlock;
// detect presence of this databases mapping in authBlock
// in that case mapUser was already invoked for it
unsigned flags = db ? 0 : FLAG_DB;
for (AuthReader rdr(authBlock); rdr.getInfo(info); rdr.moveNext())
{
if (db && info.secDb == db)
flags |= FLAG_DB;
if (info.secDb == securityDb)
flags |= FLAG_SEC;
}
// Perform lock & map only when needed
if (flags != (FLAG_DB | FLAG_SEC))
{
AuthReader::Info info;
SyncType syncType = SYNC_SHARED;
IAttachment* iDb = NULL;
IAttachment* iSec = NULL;
FbLocalStatus st;
try
{
for (;;)
{
if (syncType == SYNC_EXCLUSIVE)
{
DispatcherPtr prov;
if (cryptCb)
{
prov->setDbCryptCallback(&st, cryptCb);
check("IProvider::setDbCryptCallback", &st);
}
ClumpletWriter embeddedSysdba(ClumpletWriter::Tagged,
MAX_DPB_SIZE, isc_dpb_version1);
embeddedSysdba.insertString(isc_dpb_user_name, SYSDBA_USER_NAME,
fb_strlen(SYSDBA_USER_NAME));
embeddedSysdba.insertByte(isc_dpb_sec_attach, TRUE);
embeddedSysdba.insertByte(isc_dpb_map_attach, TRUE);
embeddedSysdba.insertByte(isc_dpb_no_db_triggers, TRUE);
if (!iSec)
{
iSec = prov->attachDatabase(&st, securityAlias,
embeddedSysdba.getBufferLength(), embeddedSysdba.getBuffer());
if (st->getState() & IStatus::STATE_ERRORS)
{
const ISC_STATUS* s = st->getErrors();
bool missing = fb_utils::containsErrorCode(s, isc_io_error);
secDown = fb_utils::containsErrorCode(s, isc_shutdown);
if (!(missing || secDown))
check("IProvider::attachDatabase", &st);
// down/missing security DB is not a reason to fail mapping
iSec = NULL;
}
}
if (db && !iDb)
{
const char* conf = "Providers=" CURRENT_ENGINE;
embeddedSysdba.insertString(isc_dpb_config, conf, fb_strlen(conf));
if (!iDb)
{
iDb = prov->attachDatabase(&st, alias,
embeddedSysdba.getBufferLength(), embeddedSysdba.getBuffer());
if (st->getState() & IStatus::STATE_ERRORS)
{
const ISC_STATUS* s = st->getErrors();
bool missing = fb_utils::containsErrorCode(s, isc_io_error);
dbDown = fb_utils::containsErrorCode(s, isc_shutdown);
if (!(missing || dbDown))
check("IProvider::attachDatabase", &st);
// down/missing DB is not a reason to fail mapping
iDb = NULL;
}
}
}
}
MutexEnsureUnlock g(treeMutex, FB_FUNCTION);
g.enter();
Cache* cDb = NULL;
if (db)
cDb = locate(alias, db);
Cache* cSec = locate(securityAlias, securityDb);
if (cDb == cSec)
cDb = NULL;
SyncObject dummySync1, dummySync2;
Sync sDb(((!(flags & FLAG_DB)) && cDb) ? &cDb->syncObject : &dummySync1, FB_FUNCTION);
Sync sSec((!(flags & FLAG_SEC)) ? &cSec->syncObject : &dummySync2, FB_FUNCTION);
sSec.lock(syncType);
if (!sDb.lockConditional(syncType))
{
// Avoid deadlocks cause hell knows which db is security for which
sSec.unlock();
// Now safely wait for sSec
sDb.lock(syncType);
// and repeat whole operation
continue;
}
// Required cache(s) are locked somehow - release treeMutex
g.leave();
// Check is it required to populate caches from DB
if ((cDb && !cDb->dataFlag) || !cSec->dataFlag)
{
if (syncType != SYNC_EXCLUSIVE)
{
syncType = SYNC_EXCLUSIVE;
sSec.unlock();
sDb.unlock();
continue;
}
if (cDb)
cDb->populate(iDb, dbDown);
cSec->populate(iSec, secDown);
sSec.downgrade(SYNC_SHARED);
sDb.downgrade(SYNC_SHARED);
}
// use down flags from caches
if (cDb)
dbDown = cDb->downFlag;
secDown = cSec->downFlag;
// Caches are ready somehow - proceed with analysis
AuthReader auth(authBlock);
// Map in simple mode first main, next security db
if (cDb && cDb->map4(false, flags & FLAG_DB, auth, info, newBlock))
break;
if (cSec->map4(false, flags & FLAG_SEC, auth, info, newBlock))
break;
// Map in wildcard mode first main, next security db
if (cDb && cDb->map4(true, flags & FLAG_DB, auth, info, newBlock))
break;
cSec->map4(true, flags & FLAG_SEC, auth, info, newBlock);
break;
}
if (iDb)
{
iDb->detach(&st);
check("IAttachment::detach", &st);
iDb = NULL;
}
if (iSec)
{
iSec->detach(&st);
check("IAttachment::detach", &st);
iSec = NULL;
}
}
catch (const Exception&)
{
if (iDb)
iDb->release();
if (iSec)
iSec->release();
throw;
}
for (AuthReader rdr(newBlock); rdr.getInfo(info); rdr.moveNext())
{
if (db && info.secDb == db)
flags |= FLAG_DB;
if (info.secDb == securityDb)
flags |= FLAG_SEC;
}
// mark both DBs as 'seen'
info.plugin = "";
info.name = "";
info.type = TYPE_SEEN;
if (!(flags & FLAG_DB))
{
info.secDb = db;
newBlock.add(info);
}
if (!(flags & FLAG_SEC))
{
info.secDb = securityDb;
newBlock.add(info);
}
}
newBlock.append(authBlock);
Found fName, fRole;
MAP_DEBUG(fprintf(stderr, "Starting newblock scan\n"));
for (AuthReader scan(newBlock); scan.getInfo(info); scan.moveNext())
{
MAP_DEBUG(fprintf(stderr, "Newblock info: secDb=%s plugin=%s type=%s name=%s origPlug=%s\n",
info.secDb.c_str(), info.plugin.c_str(), info.type.c_str(), info.name.c_str(), info.origPlug.c_str()));
Found::What recordWeight =
(db && info.secDb == db) ? Found::FND_DB :
(info.secDb == securityDb) ? Found::FND_SEC :
Found::FND_NOTHING;
if (recordWeight != Found::FND_NOTHING)
{
if (info.type == NM_USER)
fName.set(recordWeight, info);
else if (info.type == NM_ROLE)
fRole.set(recordWeight, info);
}
}
if (fName.found == Found::FND_NOTHING)
{
Arg::Gds v(isc_sec_context);
v << alias;
if (secDown || dbDown)
v << Arg::Gds(isc_map_down);
v.raise();
}
name = fName.value.ToString();
trusted_role = fRole.value.ToString();
MAP_DEBUG(fprintf(stderr, "login=%s tr=%s\n", name.c_str(), trusted_role.c_str()));
if (auth_method)
*auth_method = fName.method.ToString();
if (newAuthBlock)
{
newAuthBlock->shrink(0);
newAuthBlock->push(newBlock.getBuffer(), newBlock.getBufferLength());
MAP_DEBUG(fprintf(stderr, "Saved to newAuthBlock %u bytes\n",
static_cast<unsigned>(newAuthBlock->getCount())));
}
return secDown || dbDown;
}
void clearMap(const char* dbName)
{
mappingIpc->clearMap(dbName);
}
const Format* GlobalMappingScan::getFormat(thread_db* tdbb, jrd_rel* relation) const
{
jrd_tra* const transaction = tdbb->getTransaction();
return transaction->getMappingList()->getList(tdbb, relation)->getFormat();
}
bool GlobalMappingScan::retrieveRecord(thread_db* tdbb, jrd_rel* relation,
FB_UINT64 position, Record* record) const
{
jrd_tra* const transaction = tdbb->getTransaction();
return transaction->getMappingList()->getList(tdbb, relation)->fetch(position, record);
}
MappingList::MappingList(jrd_tra* tra)
: SnapshotData(*tra->tra_pool)
{ }
RecordBuffer* MappingList::makeBuffer(thread_db* tdbb)
{
MemoryPool* const pool = tdbb->getTransaction()->tra_pool;
allocBuffer(tdbb, *pool, rel_global_auth_mapping);
return getData(rel_global_auth_mapping);
}
RecordBuffer* MappingList::getList(thread_db* tdbb, jrd_rel* relation)
{
fb_assert(relation);
fb_assert(relation->rel_id == rel_global_auth_mapping);
RecordBuffer* buffer = getData(relation);
if (buffer)
{
return buffer;
}
FbLocalStatus st;
DispatcherPtr prov;
IAttachment* att = NULL;
ITransaction* tra = NULL;
IResultSet* curs = NULL;
try
{
ClumpletWriter embeddedSysdba(ClumpletWriter::Tagged,
MAX_DPB_SIZE, isc_dpb_version1);
embeddedSysdba.insertString(isc_dpb_user_name, SYSDBA_USER_NAME,
fb_strlen(SYSDBA_USER_NAME));
embeddedSysdba.insertByte(isc_dpb_sec_attach, TRUE);
embeddedSysdba.insertByte(isc_dpb_no_db_triggers, TRUE);
const char* dbName = tdbb->getDatabase()->dbb_config->getSecurityDatabase();
att = prov->attachDatabase(&st, dbName,
embeddedSysdba.getBufferLength(), embeddedSysdba.getBuffer());
if (st->getState() & IStatus::STATE_ERRORS)
{
if (!fb_utils::containsErrorCode(st->getErrors(), isc_io_error))
check("IProvider::attachDatabase", &st);
// In embedded mode we are not raising any errors - silent return
if (MasterInterfacePtr()->serverMode(-1) < 0)
return makeBuffer(tdbb);
(Arg::Gds(isc_map_nodb) << dbName).raise();
}
ClumpletWriter readOnly(ClumpletWriter::Tpb, MAX_DPB_SIZE, isc_tpb_version1);
readOnly.insertTag(isc_tpb_read);
readOnly.insertTag(isc_tpb_wait);
tra = att->startTransaction(&st, readOnly.getBufferLength(), readOnly.getBuffer());
check("IAttachment::startTransaction", &st);
Message mMap;
Field<Varying> name(mMap, MAX_SQL_IDENTIFIER_SIZE);
Field<Text> usng(mMap, 1);
Field<Varying> plugin(mMap, MAX_SQL_IDENTIFIER_SIZE);
Field<Varying> db(mMap, MAX_SQL_IDENTIFIER_SIZE);
Field<Varying> fromType(mMap, MAX_SQL_IDENTIFIER_SIZE);
Field<Varying> from(mMap, 255);
Field<SSHORT> role(mMap);
Field<Varying> to(mMap, MAX_SQL_IDENTIFIER_SIZE);
curs = att->openCursor(&st, tra, 0,
"SELECT RDB$MAP_NAME, RDB$MAP_USING, RDB$MAP_PLUGIN, RDB$MAP_DB, "
" RDB$MAP_FROM_TYPE, RDB$MAP_FROM, RDB$MAP_TO_TYPE, RDB$MAP_TO "
"FROM RDB$AUTH_MAPPING",
3, NULL, NULL, mMap.getMetadata(), NULL, 0);
if (st->getState() & IStatus::STATE_ERRORS)
{
if (!fb_utils::containsErrorCode(st->getErrors(), isc_dsql_relation_err))
check("IAttachment::openCursor", &st);
// isc_dsql_relation_err when opening cursor - i.e. table RDB$AUTH_MAPPING
// is missing due to non-FB3 security DB
tra->release();
att->detach(&st);
// In embedded mode we are not raising any errors - silent return
if (MasterInterfacePtr()->serverMode(-1) < 0)
return makeBuffer(tdbb);
(Arg::Gds(isc_map_notable) << dbName).raise();
}
buffer = makeBuffer(tdbb);
Record* record = buffer->getTempRecord();
while (curs->fetchNext(&st, mMap.getBuffer()) == IStatus::RESULT_OK)
{
int charset = CS_METADATA;
record->nullify();
putField(tdbb, record,
DumpField(f_sec_map_name, VALUE_STRING, name->len, name->data),
charset);
putField(tdbb, record,
DumpField(f_sec_map_using, VALUE_STRING, 1, usng->data),
charset);
if (!plugin.null)
{
putField(tdbb, record,
DumpField(f_sec_map_plugin, VALUE_STRING, plugin->len, plugin->data),
charset);
}
if (!db.null)
{
putField(tdbb, record,
DumpField(f_sec_map_db, VALUE_STRING, db->len, db->data),
charset);
}
if (!fromType.null)
{
putField(tdbb, record,
DumpField(f_sec_map_from_type, VALUE_STRING, fromType->len, fromType->data),
charset);
}
if (!from.null)
{
putField(tdbb, record,
DumpField(f_sec_map_from, VALUE_STRING, from->len, from->data),
charset);
}
if (!role.null)
{
SINT64 v = role;
putField(tdbb, record,
DumpField(f_sec_map_to_type, VALUE_INTEGER, sizeof(v), &v),
charset);
}
if (!to.null)
{
putField(tdbb, record,
DumpField(f_sec_map_to, VALUE_STRING, to->len, to->data),
charset);
}
buffer->store(record);
}
check("IResultSet::fetchNext", &st);
curs->close(&st);
check("IResultSet::close", &st);
curs = NULL;
tra->rollback(&st);
check("ITransaction::rollback", &st);
tra = NULL;
att->detach(&st);
check("IAttachment::detach", &st);
att = NULL;
}
catch (const Exception&)
{
if (curs)
curs->release();
if (tra)
tra->release();
if (att)
att->detach(&st);
clearSnapshot();
throw;
}
return getData(relation);
}
} // namespace Jrd