Files
pvAccess/src/remote/security.cpp
2019-10-31 07:43:48 -07:00

330 lines
9.4 KiB
C++

/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvAccessCPP is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#include <osiProcess.h>
#include <epicsThread.h>
#include <epicsGuard.h>
#include <pv/epicsException.h>
#include <pv/reftrack.h>
#define epicsExportSharedSymbols
#include <pv/securityImpl.h>
typedef epicsGuard<epicsMutex> Guard;
namespace {
namespace pvd = epics::pvData;
namespace pva = epics::pvAccess;
pvd::StructureConstPtr userAndHostStructure(
pvd::FieldBuilder::begin()->
add("user", pvd::pvString)->
add("host", pvd::pvString)->
createStructure()
);
struct SimpleSession : public pva::AuthenticationSession
{
const pvd::PVStructure::const_shared_pointer initdata;
SimpleSession(const pvd::PVStructure::const_shared_pointer& data) :initdata(data) {}
virtual ~SimpleSession() {}
virtual epics::pvData::PVStructure::const_shared_pointer initializationData() OVERRIDE FINAL
{ return initdata; }
};
struct AnonPlugin : public pva::AuthenticationPlugin
{
const bool server;
AnonPlugin(bool server) :server(server) {}
virtual ~AnonPlugin() {}
virtual std::tr1::shared_ptr<pva::AuthenticationSession> createSession(
const std::tr1::shared_ptr<pva::PeerInfo>& peer,
std::tr1::shared_ptr<pva::AuthenticationPluginControl> const & control,
epics::pvData::PVStructure::shared_pointer const & data) OVERRIDE FINAL
{
std::tr1::shared_ptr<SimpleSession> sess(new SimpleSession(pvd::PVStructure::const_shared_pointer())); // no init data
if(server) {
peer->identified = false;
peer->account = "anonymous";
control->authenticationCompleted(pvd::Status::Ok, peer);
}
return sess;
}
};
struct CAPlugin : public pva::AuthenticationPlugin
{
const bool server;
// fully const after ctor
const pvd::PVStructurePtr user;
CAPlugin(bool server)
:server(server)
,user(userAndHostStructure->build())
{
std::vector<char> buffer(256u);
if(osiGetUserName(&buffer[0], buffer.size()) != osiGetUserNameSuccess)
throw std::runtime_error("Unable to determine user account name");
buffer[buffer.size()-1] = '\0';
user->getSubFieldT<pvd::PVString>("user")->put(&buffer[0]);
// use of unverified host name is considered deprecated.
// use PeerInfo::peer instead.
if (gethostname(&buffer[0], buffer.size()) != 0)
throw std::runtime_error("Unable to determine host name");
buffer[buffer.size()-1] = '\0';
user->getSubFieldT<pvd::PVString>("host")->put(&buffer[0]);
}
virtual ~CAPlugin() {}
virtual std::tr1::shared_ptr<pva::AuthenticationSession> createSession(
const std::tr1::shared_ptr<pva::PeerInfo>& peer,
std::tr1::shared_ptr<pva::AuthenticationPluginControl> const & control,
epics::pvData::PVStructure::shared_pointer const & data) OVERRIDE FINAL
{
std::tr1::shared_ptr<SimpleSession> sess(new SimpleSession(user)); // no init data
if(server) {
pvd::PVString::shared_pointer user;
if(data)
user = data->getSubField<pvd::PVString>("user");
if(user) {
peer->account = user->get();
peer->identified = !peer->account.empty();
peer->aux = pvd::getPVDataCreate()->createPVStructure(data); // clone to ensure it won't be modified
}
control->authenticationCompleted(pvd::Status::Ok, peer);
}
return sess;
}
};
struct GroupsPlugin : public pva::AuthorizationPlugin
{
virtual ~GroupsPlugin() {}
void authorize(const std::tr1::shared_ptr<pva::PeerInfo>& peer)
{
if(!peer->identified)
return; // no groups for anonymous
pva::osdGetRoles(peer->account, peer->roles);
}
};
} // namespace
namespace epics {
namespace pvAccess {
size_t PeerInfo::num_instances;
PeerInfo::PeerInfo()
:transportVersion(0u)
,local(false)
,identified(false)
{
REFTRACE_INCREMENT(num_instances);
}
PeerInfo::~PeerInfo()
{
REFTRACE_DECREMENT(num_instances);
}
AuthenticationSession::~AuthenticationSession() {}
AuthenticationPluginControl::~AuthenticationPluginControl() {}
AuthenticationPlugin::~AuthenticationPlugin() {}
AuthenticationRegistry::~AuthenticationRegistry() {}
AuthorizationPlugin::~AuthorizationPlugin() {}
AuthorizationRegistry::~AuthorizationRegistry() {}
namespace {
struct authGbl_t {
mutable epicsMutex mutex;
AuthenticationRegistry servers, clients;
AuthorizationRegistry authorizers;
} *authGbl;
void authGblInit(void *)
{
authGbl = new authGbl_t;
{
AnonPlugin::shared_pointer plugin(new AnonPlugin(true));
authGbl->servers.add(-1024, "anonymous", plugin);
}
{
AnonPlugin::shared_pointer plugin(new AnonPlugin(false));
authGbl->clients.add(-1024, "anonymous", plugin);
}
{
CAPlugin::shared_pointer plugin(new CAPlugin(true));
authGbl->servers.add(0, "ca", plugin);
}
{
CAPlugin::shared_pointer plugin(new CAPlugin(false));
authGbl->clients.add(0, "ca", plugin);
}
{
GroupsPlugin::shared_pointer plugin(new GroupsPlugin);
authGbl->authorizers.add(0, plugin);
}
epics::registerRefCounter("PeerInfo", &PeerInfo::num_instances);
}
epicsThreadOnceId authGblOnce = EPICS_THREAD_ONCE_INIT;
} // namespace
AuthenticationRegistry& AuthenticationRegistry::clients()
{
epicsThreadOnce(&authGblOnce, &authGblInit, 0);
assert(authGbl);
return authGbl->clients;
}
AuthenticationRegistry& AuthenticationRegistry::servers()
{
epicsThreadOnce(&authGblOnce, &authGblInit, 0);
assert(authGbl);
return authGbl->servers;
}
void AuthenticationRegistry::snapshot(list_t &plugmap) const
{
plugmap.clear();
Guard G(mutex);
plugmap.reserve(map.size());
for(map_t::const_iterator it(map.begin()), end(map.end()); it!=end; ++it) {
plugmap.push_back(it->second);
}
}
void AuthenticationRegistry::add(int prio, const std::string& name,
const AuthenticationPlugin::shared_pointer& plugin)
{
Guard G(mutex);
if(map.find(prio)!=map.end())
THROW_EXCEPTION2(std::logic_error, "Authentication plugin already registered with this priority");
map[prio] = std::make_pair(name, plugin);
}
bool AuthenticationRegistry::remove(const AuthenticationPlugin::shared_pointer& plugin)
{
Guard G(mutex);
for(map_t::iterator it(map.begin()), end(map.end()); it!=end; ++it) {
if(it->second.second==plugin) {
map.erase(it);
return true;
}
}
return false;
}
AuthenticationPlugin::shared_pointer AuthenticationRegistry::lookup(const std::string& name) const
{
Guard G(mutex);
// assuming the number of plugins is small, we don't index by name.
for(map_t::const_iterator it(map.begin()), end(map.end()); it!=end; ++it) {
if(it->second.first==name)
return it->second.second;
}
return AuthenticationPlugin::shared_pointer();
}
AuthorizationRegistry::AuthorizationRegistry()
:busy(0)
{}
AuthorizationRegistry& AuthorizationRegistry::plugins()
{
epicsThreadOnce(&authGblOnce, &authGblInit, 0);
assert(authGbl);
return authGbl->authorizers;
}
void AuthorizationRegistry::add(int prio, const AuthorizationPlugin::shared_pointer& plugin)
{
Guard G(mutex);
// we don't expect changes after server start
if(busy)
throw std::runtime_error("AuthorizationRegistry busy");
if(map.find(prio)!=map.end())
THROW_EXCEPTION2(std::logic_error, "Authorization plugin already registered with this priority");
map[prio] = plugin;
}
bool AuthorizationRegistry::remove(const AuthorizationPlugin::shared_pointer& plugin)
{
Guard G(mutex);
if(busy)
throw std::runtime_error("AuthorizationRegistry busy");
for(map_t::iterator it(map.begin()), end(map.end()); it!=end; ++it) {
if(it->second==plugin) {
map.erase(it);
return true;
}
}
return false;
}
void AuthorizationRegistry::run(const std::tr1::shared_ptr<PeerInfo>& peer)
{
{
Guard G(mutex);
busy++;
}
for(map_t::iterator it(map.begin()), end(map.end()); it!=end; ++it)
{
(it->second)->authorize(peer);
}
{
Guard G(mutex);
assert(busy>=0);
busy--;
}
}
void AuthNZHandler::handleResponse(osiSockAddr* responseFrom,
Transport::shared_pointer const & transport,
epics::pvData::int8 version,
epics::pvData::int8 command,
size_t payloadSize,
epics::pvData::ByteBuffer* payloadBuffer)
{
ResponseHandler::handleResponse(responseFrom, transport, version, command, payloadSize, payloadBuffer);
pvd::PVStructure::shared_pointer data;
{
pvd::PVField::shared_pointer raw(SerializationHelper::deserializeFull(payloadBuffer, transport.get()));
if(raw->getField()->getType()==pvd::structure) {
data = std::tr1::static_pointer_cast<pvd::PVStructure>(raw);
} else {
// was originally possible, but never used
}
}
transport->authNZMessage(data);
}
}} // namespace epics::pvAccess