diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 85bb1ca..fb515da 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -773,6 +773,7 @@ INPUT = ../src/client/pv \ ../src/rpcService/pv \ ../src/utils/pv \ ../src/pva/pv \ + ../src/remote/pv \ . # This tag can be used to specify the character encoding of the source files diff --git a/src/client/pv/pvAccess.h b/src/client/pv/pvAccess.h index 74e6d3f..66edf75 100644 --- a/src/client/pv/pvAccess.h +++ b/src/client/pv/pvAccess.h @@ -819,6 +819,7 @@ public: }; +struct PeerInfo; // see pv/security.h class ChannelRequester; @@ -1133,6 +1134,26 @@ public: * @param connectionState The new connection state. */ virtual void channelStateChange(Channel::shared_pointer const & channel, Channel::ConnectionState connectionState) = 0; + + /** + * @brief Return information on connected peer if applicable. + * + * A server-type ChannelProvider will use this method to discover if a remote client + * has provided credentials which may be used in access control decisions. + * + * Default implementation returns NULL. + * + * isConnected()==true and getPeerInfo()==NULL when the ChannelProvider does not provide + * information about the peer. This should be treated as an unauthenticated, anonymous, + * peer. + * + * The returned instance must not change, and a different instance should be returned + * if/when peer information changes (eg. after reconnect). + * + * May return !NULL when !isConnected(). getPeerInfo() must be called _before_ + * testing isConnected() in situations where connection state is being polled. + */ + virtual std::tr1::shared_ptr getPeerInfo(); }; //! Used when ChannelProvider::createChannel() is passed a NULL ChannelRequester diff --git a/src/client/pvAccess.cpp b/src/client/pvAccess.cpp index 7975f74..5d2bef3 100644 --- a/src/client/pvAccess.cpp +++ b/src/client/pvAccess.cpp @@ -8,9 +8,11 @@ #include #include #include +#include #define epicsExportSharedSymbols #include +#include namespace pvd = epics::pvData; @@ -418,6 +420,11 @@ ChannelRequester::~ChannelRequester() REFTRACE_DECREMENT(num_instances); } +PeerInfo::const_shared_pointer ChannelRequester::getPeerInfo() +{ + return PeerInfo::const_shared_pointer(); +} + std::string DefaultChannelRequester::getRequesterName() { return "DefaultChannelRequester"; } void DefaultChannelRequester::channelCreated(const epics::pvData::Status& status, Channel::shared_pointer const & channel) diff --git a/src/remote/codec.cpp b/src/remote/codec.cpp index 012b029..110fcc7 100644 --- a/src/remote/codec.cpp +++ b/src/remote/codec.cpp @@ -40,6 +40,8 @@ using namespace std; using namespace epics::pvData; using namespace epics::pvAccess; +typedef epicsGuard Guard; + namespace { struct BreakTransport : TransportSender { @@ -1087,15 +1089,11 @@ void BlockingTCPTransportCodec::internalClose() Transport::shared_pointer thisSharedPtr = this->shared_from_this(); _context->getTransportRegistry()->remove(thisSharedPtr); - // TODO sync - if (_securitySession) - _securitySession->close(); - if (IS_LOGGABLE(logLevelDebug)) { LOG(logLevelDebug, "TCP socket to %s is to be closed.", - inetAddressToString(_socketAddress).c_str()); + _socketName.c_str()); } } @@ -1244,7 +1242,6 @@ BlockingTCPTransportCodec::BlockingTCPTransportCodec(bool serverFlag, const Cont ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); _socketName = ipAddrStr; } - } @@ -1361,24 +1358,28 @@ bool BlockingTCPTransportCodec::verify(epics::pvData::int32 timeoutMs) { } void BlockingTCPTransportCodec::verified(epics::pvData::Status const & status) { - epics::pvData::Lock lock(_verifiedMutex); + epics::pvData::Lock lock(_mutex); if (IS_LOGGABLE(logLevelDebug) && !status.isOK()) { - char ipAddrStr[48]; - ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); - LOG(logLevelDebug, "Failed to verify connection to %s: %s.", ipAddrStr, status.getMessage().c_str()); - // TODO stack dump + LOG(logLevelDebug, "Failed to verify connection to %s: %s.", _socketName.c_str(), status.getMessage().c_str()); } - _verified = status.isSuccess(); + { + Guard G(_mutex); + _verified = status.isSuccess(); + } _verifiedEvent.signal(); } -void BlockingTCPTransportCodec::authNZMessage(epics::pvData::PVField::shared_pointer const & data) { - // TODO sync - if (_securitySession) - _securitySession->messageReceived(data); +void BlockingTCPTransportCodec::authNZMessage(epics::pvData::PVStructure::shared_pointer const & data) { + AuthenticationSession::shared_pointer sess; + { + Guard G(_mutex); + sess = _authSession; + } + if (sess) + sess->messageReceived(data); else { char ipAddrStr[48]; @@ -1392,7 +1393,7 @@ class SecurityPluginMessageTransportSender : public TransportSender { public: POINTER_DEFINITIONS(SecurityPluginMessageTransportSender); - SecurityPluginMessageTransportSender(PVField::shared_pointer const & data) : + SecurityPluginMessageTransportSender(PVStructure::const_shared_pointer const & data) : _data(data) { } @@ -1405,11 +1406,10 @@ public: } private: - PVField::shared_pointer _data; + PVStructure::const_shared_pointer _data; }; -void BlockingTCPTransportCodec::sendSecurityPluginMessage(epics::pvData::PVField::shared_pointer const & data) { - // TODO not optimal since it allocates a new object every time +void BlockingTCPTransportCodec::sendSecurityPluginMessage(epics::pvData::PVStructure::const_shared_pointer const & data) { SecurityPluginMessageTransportSender::shared_pointer spmts(new SecurityPluginMessageTransportSender(data)); enqueueSendRequest(spmts); } @@ -1426,7 +1426,7 @@ BlockingServerTCPTransportCodec::BlockingServerTCPTransportCodec( int32_t receiveBufferSize) : BlockingTCPTransportCodec(true, context, channel, responseHandler, sendBufferSize, receiveBufferSize, PVA_DEFAULT_PRIORITY), - _lastChannelSID(0), _verifyOrVerified(false), _securityRequired(false) + _lastChannelSID(0), _verifyOrVerified(false) { // NOTE: priority not yet known, default priority is used to //register/unregister @@ -1530,29 +1530,38 @@ void BlockingServerTCPTransportCodec::send(ByteBuffer* buffer, // TODO buffer->putShort(0x7FFF); - // list of authNZ plugin names - const Context::securityPlugins_t& securityPlugins = _context->getSecurityPlugins(); - vector validSPNames; - validSPNames.reserve(securityPlugins.size()); + // list of authNZ plugin names advertised to this client - for (Context::securityPlugins_t::const_iterator iter(securityPlugins.begin()); - iter != securityPlugins.end(); iter++) + AuthenticationRegistry::list_t plugins; + AuthenticationRegistry::servers().snapshot(plugins); // copy + std::vector validSPNames; + validSPNames.reserve(plugins.size()); // assume all will be valid + + PeerInfo info; + info.transport = "pva"; + info.peer = _socketName; + info.transportVersion = this->getRevision(); + + // filter plugins which may be used by this peer + for(AuthenticationRegistry::list_t::iterator it(plugins.begin()), end(plugins.end()); + it!=end; ++it) { - SecurityPlugin::shared_pointer securityPlugin = iter->second; - if (securityPlugin->isValidFor(_socketAddress)) - validSPNames.push_back(securityPlugin->getId()); + info.authority = it->first; + if(it->second->isValidFor(info)) + validSPNames.push_back(it->first); } - size_t validSPCount = validSPNames.size(); - - SerializeHelper::writeSize(validSPCount, buffer, this); - for (vector::const_iterator iter = - validSPNames.begin(); - iter != validSPNames.end(); iter++) + SerializeHelper::writeSize(validSPNames.size(), buffer, this); + for (vector::const_iterator iter(validSPNames.begin()), end(validSPNames.end()); + iter != end; iter++) + { SerializeHelper::serializeString(*iter, buffer, this); + } - // TODO sync - _securityRequired = (validSPCount > 0); + { + Guard G(_mutex); + advertisedAuthPlugins.swap(validSPNames); + } // send immediately control->flush(true); @@ -1564,10 +1573,12 @@ void BlockingServerTCPTransportCodec::send(ByteBuffer* buffer, // control->startMessage(CMD_CONNECTION_VALIDATED, 0); + pvData::Status sts; { - Lock lock(_verificationStatusMutex); - _verificationStatus.serialize(buffer, control); + Lock lock(_mutex); + sts = _verificationStatus; } + sts.serialize(buffer, control); // send immediately control->flush(true); @@ -1600,14 +1611,25 @@ void BlockingServerTCPTransportCodec::internalClose() { destroyAllChannels(); } -void BlockingServerTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status) +void BlockingServerTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status, + const std::tr1::shared_ptr& peer) { if (IS_LOGGABLE(logLevelDebug)) { LOG(logLevelDebug, "Authentication completed with status '%s' for PVA client: %s.", Status::StatusTypeName[status.getType()], _socketName.c_str()); } - if (!isVerified()) // TODO sync + bool isVerified; + { + Guard G(_mutex); + isVerified = _verified; + if(status.isSuccess()) + _peerInfo = peer; + else + _peerInfo.reset(); + } + + if (!isVerified) verified(status); else if (!status.isSuccess()) { @@ -1620,59 +1642,35 @@ void BlockingServerTCPTransportCodec::authenticationCompleted(epics::pvData::Sta } } -epics::pvData::Status BlockingServerTCPTransportCodec::invalidSecurityPluginNameStatus(Status::STATUSTYPE_ERROR, "invalid security plug-in name"); - void BlockingServerTCPTransportCodec::authNZInitialize(const std::string& securityPluginName, - const epics::pvData::PVField::shared_pointer& data) + const epics::pvData::PVStructure::shared_pointer& data) { - // check if plug-in name is valid - SecurityPlugin::shared_pointer securityPlugin; + AuthenticationPlugin::shared_pointer plugin(AuthenticationRegistry::servers().lookup(securityPluginName)); + // attempting the force use of an un-advertised/non-existant plugin is treated as a protocol error. + // We cheat here by assuming the the registry doesn't often change after server start, + // and don't test if securityPluginName is in advertisedAuthPlugins + if(!plugin) + throw std::runtime_error(_socketName+" failing attempt to select non-existant auth. plugin "+securityPluginName); - Context::securityPlugins_t::const_iterator spIter(_context->getSecurityPlugins().find(securityPluginName)); - if (spIter != _context->getSecurityPlugins().end()) - securityPlugin = spIter->second; - if (!securityPlugin) - { - if (_securityRequired) - { - verified(invalidSecurityPluginNameStatus); - return; - } - else - { - securityPlugin = NoSecurityPlugin::INSTANCE; + PeerInfo::shared_pointer info(new PeerInfo); + info->peer = _socketName; + info->transport = "pva"; + info->transportVersion = getRevision(); + info->authority = securityPluginName; - if (IS_LOGGABLE(logLevelDebug)) - { - LOG(logLevelDebug, "No security plug-in installed, selecting default plug-in '%s' for PVA client: %s.", securityPlugin->getId().c_str(), _socketName.c_str()); - } - } - } - - if (!securityPlugin->isValidFor(_socketAddress)) - verified(invalidSecurityPluginNameStatus); + if (!plugin->isValidFor(*info)) + verified(pvData::Status::error("invalid security plug-in name")); if (IS_LOGGABLE(logLevelDebug)) { - char ipAddrStr[48]; - ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); - LOG(logLevelDebug, "Accepted security plug-in '%s' for PVA client: %s.", securityPluginName.c_str(), ipAddrStr); + LOG(logLevelDebug, "Accepted security plug-in '%s' for PVA client: %s.", securityPluginName.c_str(), _socketName.c_str()); } - try - { - // create session - SecurityPluginControl::shared_pointer spc = std::tr1::dynamic_pointer_cast(shared_from_this()); - // TODO sync - _securitySession = securityPlugin->createSession(_socketAddress, spc, data); - } catch (SecurityException &se) { - if (IS_LOGGABLE(logLevelDebug)) - { - LOG(logLevelDebug, "Security plug-in '%s' failed to create a session for PVA client: %s.", securityPluginName.c_str(), _socketName.c_str()); - } - Status status(Status::STATUSTYPE_ERROR, se.what()); - verified(status); - } + AuthenticationSession::shared_pointer sess(plugin->createSession(info, shared_from_this(), data)); + + Guard G(_mutex); + _authSessionName = securityPluginName; + _authSession.swap(sess); } @@ -1894,17 +1892,25 @@ void BlockingClientTCPTransportCodec::send(ByteBuffer* buffer, // QoS (aka connection priority) buffer->putShort(getPriority()); - // TODO sync - if (_securitySession) + std::string pluginName; + AuthenticationSession::shared_pointer session; + { + Guard G(_mutex); + pluginName = _authSessionName; + session = _authSession; + } + + if (session) { // selected authNZ plug-in name - SerializeHelper::serializeString(_securitySession->getSecurityPlugin()->getId(), buffer, control); + SerializeHelper::serializeString(_authSessionName, buffer, control); // optional authNZ plug-in initialization data - SerializationHelper::serializeFull(buffer, control, _securitySession->initializationData()); + SerializationHelper::serializeFull(buffer, control, session->initializationData()); } else { + //TODO: allowed? // emptry authNZ plug-in name SerializeHelper::serializeString("", buffer, control); @@ -1926,38 +1932,68 @@ void BlockingClientTCPTransportCodec::send(ByteBuffer* buffer, void BlockingClientTCPTransportCodec::authNZInitialize(const std::vector& offeredSecurityPlugins) { - if (!offeredSecurityPlugins.empty()) + AuthenticationRegistry& plugins = AuthenticationRegistry::clients(); + std::string selectedName; + AuthenticationPlugin::shared_pointer plugin; + + // because of a missing break; the original SecurityPlugin effectively treated the offered list as being + // in order of increasing preference (last is preferred). + // we continue with this because, hey isn't compatibility fun... + + for(std::vector::const_reverse_iterator it(offeredSecurityPlugins.rbegin()), end(offeredSecurityPlugins.rend()); + it!=end; ++it) { - const Context::securityPlugins_t& availableSecurityPlugins(_context->getSecurityPlugins()); - - for (vector::const_iterator offeredSP = offeredSecurityPlugins.begin(); - offeredSP != offeredSecurityPlugins.end(); offeredSP++) - { - Context::securityPlugins_t::const_iterator spi(availableSecurityPlugins.find(*offeredSP)); - if (spi != availableSecurityPlugins.end()) - { - SecurityPlugin::shared_pointer securityPlugin = spi->second; - if (securityPlugin->isValidFor(_socketAddress)) - { - // create session - SecurityPluginControl::shared_pointer spc = std::tr1::dynamic_pointer_cast(shared_from_this()); - - // TODO sync - _securitySession = securityPlugin->createSession(_socketAddress, spc, PVField::shared_pointer()); - } - } + plugin = plugins.lookup(*it); + if(plugin) { + selectedName = *it; + break; } } + if(!plugin) { + // mis-match and legacy. some early servers (java?) don't advertise any plugins. + // treat this as anonymous + selectedName = "anonymous"; + plugin = plugins.lookup(selectedName); + assert(plugin); // fallback required + } + + { + PeerInfo::shared_pointer info(new PeerInfo); + info->peer = _socketName; // this is the server name + info->transport = "pva"; + info->transportVersion = getRevision(); + info->authority = selectedName; + + AuthenticationSession::shared_pointer sess(plugin->createSession(info, shared_from_this(), pvData::PVStructure::shared_pointer())); + + Guard G(_mutex); + _authSessionName = selectedName; + _authSession = sess; + } + TransportSender::shared_pointer transportSender = std::tr1::dynamic_pointer_cast(shared_from_this()); enqueueSendRequest(transportSender); } -void BlockingClientTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status) +void BlockingClientTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status, + const std::tr1::shared_ptr& peer) { // noop for client side (server will send ConnectionValidation message) } +void BlockingClientTCPTransportCodec::verified(epics::pvData::Status const & status) +{ + AuthenticationSession::shared_pointer sess; + { + Guard G(_mutex); + sess = _authSession; + } + if(sess) + sess->authenticationComplete(status); + this->BlockingTCPTransportCodec::verified(status); +} + } } } diff --git a/src/remote/pv/blockingUDP.h b/src/remote/pv/blockingUDP.h index 9f55558..d84b3c4 100644 --- a/src/remote/pv/blockingUDP.h +++ b/src/remote/pv/blockingUDP.h @@ -124,14 +124,10 @@ public: // noop } - virtual void authNZMessage(epics::pvData::PVField::shared_pointer const & data) OVERRIDE FINAL { + virtual void authNZMessage(epics::pvData::PVStructure::shared_pointer const & data) OVERRIDE FINAL { // noop } - virtual std::tr1::shared_ptr getSecuritySession() const OVERRIDE FINAL { - return std::tr1::shared_ptr(); - } - // NOTE: this is not yet used for UDP virtual void setByteOrder(int byteOrder) OVERRIDE FINAL { // called from receive thread... or before processing diff --git a/src/remote/pv/codec.h b/src/remote/pv/codec.h index e792bdf..2b2f910 100644 --- a/src/remote/pv/codec.h +++ b/src/remote/pv/codec.h @@ -296,7 +296,7 @@ private: class BlockingTCPTransportCodec: public AbstractCodec, - public SecurityPluginControl, + public AuthenticationPluginControl, public std::tr1::enable_shared_from_this { @@ -428,18 +428,9 @@ public: virtual void verified(epics::pvData::Status const & status) OVERRIDE; - bool isVerified() const { - return _verified; // TODO sync - } + virtual void authNZMessage(epics::pvData::PVStructure::shared_pointer const & data) OVERRIDE FINAL; - virtual std::tr1::shared_ptr getSecuritySession() const OVERRIDE FINAL { - // TODO sync - return _securitySession; - } - - virtual void authNZMessage(epics::pvData::PVField::shared_pointer const & data) OVERRIDE FINAL; - - virtual void sendSecurityPluginMessage(epics::pvData::PVField::shared_pointer const & data) OVERRIDE FINAL; + virtual void sendSecurityPluginMessage(epics::pvData::PVStructure::const_shared_pointer const & data) OVERRIDE FINAL; private: void receiveThread(); @@ -467,7 +458,12 @@ protected: IntrospectionRegistry _incomingIR; IntrospectionRegistry _outgoingIR; - SecuritySession::shared_pointer _securitySession; + // active authentication exchange, if any + std::string _authSessionName; + AuthenticationSession::shared_pointer _authSession; +public: + // final info, after authentication complete. + PeerInfo::const_shared_pointer _peerInfo; private: @@ -476,9 +472,12 @@ private: epics::pvData::int8 _remoteTransportRevision; epics::pvData::int16 _priority; +protected: bool _verified; - epics::pvData::Mutex _verifiedMutex; epics::pvData::Event _verifiedEvent; + +public: + mutable epics::pvData::Mutex _mutex; }; class BlockingServerTCPTransportCodec : @@ -554,9 +553,10 @@ public: } virtual void verified(epics::pvData::Status const & status) OVERRIDE FINAL { - _verificationStatusMutex.lock(); - _verificationStatus = status; - _verificationStatusMutex.unlock(); + { + epicsGuard G(_mutex); + _verificationStatus = status; + } BlockingTCPTransportCodec::verified(status); } @@ -565,9 +565,10 @@ public: } void authNZInitialize(const std::string& securityPluginName, - const epics::pvData::PVField::shared_pointer& data); + const epics::pvData::PVStructure::shared_pointer& data); - virtual void authenticationCompleted(epics::pvData::Status const & status) OVERRIDE FINAL; + virtual void authenticationCompleted(epics::pvData::Status const & status, + const std::tr1::shared_ptr& peer) OVERRIDE FINAL; virtual void send(epics::pvData::ByteBuffer* buffer, TransportSendControl* control) OVERRIDE FINAL; @@ -595,13 +596,10 @@ private: mutable epics::pvData::Mutex _channelsMutex; epics::pvData::Status _verificationStatus; - epics::pvData::Mutex _verificationStatusMutex; bool _verifyOrVerified; - bool _securityRequired; - - static epics::pvData::Status invalidSecurityPluginNameStatus; + std::vector advertisedAuthPlugins; }; @@ -673,8 +671,10 @@ public: void authNZInitialize(const std::vector& offeredSecurityPlugins); - virtual void authenticationCompleted(epics::pvData::Status const & status) OVERRIDE FINAL; + virtual void authenticationCompleted(epics::pvData::Status const & status, + const std::tr1::shared_ptr& peer) OVERRIDE FINAL; + virtual void verified(epics::pvData::Status const & status) OVERRIDE FINAL; protected: virtual void internalClose() OVERRIDE FINAL; @@ -719,8 +719,6 @@ private: * Responsive transport notify. */ void responsiveTransport(); - - epics::pvData::Mutex _mutex; }; } diff --git a/src/remote/pv/remote.h b/src/remote/pv/remote.h index dcfc378..d41b39b 100644 --- a/src/remote/pv/remote.h +++ b/src/remote/pv/remote.h @@ -291,13 +291,12 @@ public: * Pass data to the active security plug-in session. * @param data the data (any data), can be null. */ - virtual void authNZMessage(epics::pvData::PVField::shared_pointer const & data) = 0; - - virtual std::tr1::shared_ptr getSecuritySession() const = 0; + virtual void authNZMessage(epics::pvData::PVStructure::shared_pointer const & data) = 0; }; class Channel; class SecurityPlugin; +class AuthenticationRegistry; /** * Not public IF, used by Transports, etc. @@ -317,14 +316,6 @@ public: virtual Configuration::const_shared_pointer getConfiguration() = 0; - typedef std::map > securityPlugins_t; - /** - * Get map of available security plug-ins. - * @return the map of available security plug-ins - */ - virtual const securityPlugins_t& getSecurityPlugins() = 0; - - /// /// due to ClientContextImpl /// diff --git a/src/remote/pv/security.h b/src/remote/pv/security.h index fb247c0..1af1d7b 100644 --- a/src/remote/pv/security.h +++ b/src/remote/pv/security.h @@ -7,6 +7,72 @@ #ifndef SECURITY_H #define SECURITY_H +/** @page pva_security PVA Security + * + * Summary of PVA auth. process. + * + @msc + arcgradient = 8; + + CP [label="Client Plugin"], CS [label="Client Session"], C [label="Client"], S [label="Server"], SS [label="Server Session"], SP [label="Server Plugin"]; + + C:>S [label="Opens TCP connection"]; + S box SP [label="Server lists plugins"]; + S=>SP [label="isValidFor()"]; + C<:S [label="CONNECTION_VALIDATION"]; + CP box C [label="Client lists plugins"]; + CP box C [label="Client select plugin"]; + CP<=C [label="createSession()"]; + CP>>C [label="Returns Session"]; + CS<=C [label="initializationData()"]; + CS>>C [label="Initial payload"]; + C:>S [label="CONNECTION_VALIDATION"]; + S=>SP [label="createSession()"]; + S<C [label="sendSecurityPluginMessage()"]; + C:>S [label="AUTHZ"]; + S=>SS [label="messageReceived()"]; + ...; + --- [label="Completion"]; + S<=SS [label="authenticationCompleted()"]; + C<:S [label="CONNECTION_VALIDATED"]; + CS<=C [label="authenticationComplete()"]; + @endmsc + * + * Ownership + * + @dot + digraph authplugin { + External; + AuthenticationRegistry [shape=box]; + AuthenticationPlugin [shape=box]; + AuthenticationPluginControl [shape=box]; + AuthenticationSession [shape=box]; + External -> AuthenticationRegistry; + AuthenticationRegistry -> AuthenticationPlugin; + External -> AuthenticationSession; + AuthenticationSession -> AuthenticationPluginControl [color=green, style=dashed]; + External -> AuthenticationPluginControl; + AuthenticationPluginControl -> AuthenticationSession; + AuthenticationPlugin -> AuthenticationSession [style=dashed]; + } + @enddot + * + * Locking + * + * All methods of AuthenticationSession are called from a single thread. + * Methods of AuthenticationPlugin and AuthenticationPluginControl can be called + * from any threads. + * + * AuthenticationPluginControl is an Operation, AuthenticationSession is a Requester. + * @see provider_roles_requester_locking + */ + #ifdef epicsExportSharedSymbols # define securityEpicsExportSharedSymbols # undef epicsExportSharedSymbols @@ -14,6 +80,7 @@ #include #include +#include #include #include @@ -34,426 +101,166 @@ namespace epics { namespace pvAccess { -// notify client only on demand, configurable via pvRequest -// add the following method to ChannelRequest: -// void credentialsChanged(std::vector credentials); +/** @brief Information provded by a client to a server-type ChannelProvider. + * + * All peers must be identified by a peer name, which may be a network address (IP+port#) and transport. + * Peer names must be unique for a given transport. + * + * Transport names include: + * + * # "local" in-process client. Peer name is optional and arbitrary. Must set local flag. + * # "pva" PVA over TCP. Used by PVA client provider. Peer name is IP address and TCP port number as "XXX.XXX.XXX.XXX:YYYYY". + * + * Authority names include: + * + * "anonymous" - No credentials provided. Must not set identified flag. + * "plain" - Unauthenticated credential. + */ +struct epicsShareClass PeerInfo { + POINTER_DEFINITIONS(PeerInfo); + static size_t num_instances; -// pvAccess message: channel client id, ioid (if invalid then it's for channel) and array of bitSets -// or leave to the plugin? + std::string peer; //!< network address of remote peer. eg. "192.168.1.1:5075". + std::string transport; //!< transport protocol used eg. "pva". Must not be empty. + std::string authority; //!< authentication mechanism used. eg. "anonymous" or "gssapi". Must not be empty. + std::string realm; //!< scope of authority. eg. "mylab.gov" + std::string account; //!< aka. user name + //! NULL or extra authority specific information. + pvData::PVStructure::const_shared_pointer aux; -// when clients gets initial credentialsChanged call before create is called -// and then on each change + typedef std::set roles_t; + //! Set of strings which may be used to modify access control decisions. + roles_t roles; -class epicsShareClass ChannelSecuritySession { -public: - POINTER_DEFINITIONS(ChannelSecuritySession); + unsigned transportVersion; //!< If applicable, the protocol minor version number - virtual ~ChannelSecuritySession() {} + // attributes for programatic consumption + bool local; //!< Short-hand for transport=="local" + bool identified; //!< Short-hand for authority!="anonymous" - /// closes this session - virtual void close() = 0; - - // for every authroizeCreate... a release() must be called - virtual void release(pvAccessID ioid) = 0; - - // bitSet w/ one bit - virtual epics::pvData::Status authorizeCreateChannelProcess( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & pvRequest) = 0; - virtual epics::pvData::Status authorizeProcess(pvAccessID ioid) = 0; - - // bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - virtual epics::pvData::Status authorizeCreateChannelGet( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & pvRequest) = 0; - virtual epics::pvData::Status authorizeGet(pvAccessID ioid) = 0; - - // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - virtual epics::pvData::Status authorizeCreateChannelPut( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & pvRequest) = 0; - virtual epics::pvData::Status authorizePut( - pvAccessID ioid, - epics::pvData::PVStructure::shared_pointer const & dataToPut, - epics::pvData::BitSet::shared_pointer const & fieldsToPut) = 0; - - // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - // process: bitSet w/ one bit (allowed, not allowed) - virtual epics::pvData::Status authorizeCreateChannelPutGet( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & pvRequest) = 0; - virtual epics::pvData::Status authorizePutGet( - pvAccessID ioid, - epics::pvData::PVStructure::shared_pointer const & dataToPut, - epics::pvData::BitSet::shared_pointer const & fieldsToPut) = 0; - - // bitSet w/ one bit - virtual epics::pvData::Status authorizeCreateChannelRPC( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & pvRequest) = 0; - // one could authorize per operation basis - virtual epics::pvData::Status authorizeRPC( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & arguments) = 0; - - // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - virtual epics::pvData::Status authorizeCreateMonitor( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & pvRequest) = 0; - virtual epics::pvData::Status authorizeMonitor(pvAccessID ioid) = 0; - - // read: bitSet w/ one bit (allowed, not allowed) and rest put/get/set length - virtual epics::pvData::Status authorizeCreateChannelArray( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & pvRequest) = 0; - // use authorizeGet - virtual epics::pvData::Status authorizePut(pvAccessID ioid, epics::pvData::PVArray::shared_pointer const & dataToPut) = 0; - virtual epics::pvData::Status authorizeSetLength(pvAccessID ioid) = 0; - - - // introspection authorization - virtual epics::pvData::Status authorizeGetField(pvAccessID ioid, std::string const & subField) = 0; + PeerInfo(); + virtual ~PeerInfo(); }; -class SecurityPlugin; - -class SecurityException: public std::runtime_error { -public: - explicit SecurityException(std::string const & what): std::runtime_error(what) {} -}; - -class epicsShareClass SecuritySession { -public: - POINTER_DEFINITIONS(SecuritySession); - - virtual ~SecuritySession() {} - - // optional (can be null) initialization data for the remote party - // client to server - virtual epics::pvData::PVField::shared_pointer initializationData() = 0; - - // get parent - virtual std::tr1::shared_ptr getSecurityPlugin() = 0; - - // can be called any time, for any reason - virtual void messageReceived(epics::pvData::PVField::shared_pointer const & data) = 0; - - /// closes this session - virtual void close() = 0; - - // notification to the client on allowed requests (bitSet, a bit per request) - virtual ChannelSecuritySession::shared_pointer createChannelSession(std::string const & channelName) = 0; -}; - -class epicsShareClass SecurityPluginControl { -public: - POINTER_DEFINITIONS(SecurityPluginControl); - - virtual ~SecurityPluginControl() {} - - // can be called any time, for any reason - virtual void sendSecurityPluginMessage(epics::pvData::PVField::shared_pointer const & data) = 0; - - // if Status.isSuccess() == false, - // pvAccess will send status to the client and close the connection - // can be called more then once (in case of re-authentication process) - virtual void authenticationCompleted(epics::pvData::Status const & status) = 0; -}; - - -class epicsShareClass SecurityPlugin { -public: - POINTER_DEFINITIONS(SecurityPlugin); - - virtual ~SecurityPlugin() {} - - /** - * Short, unique name for the plug-in, used to identify the plugin. - * @return the ID. - */ - virtual std::string getId() const = 0; - - /** - * Description of the security plug-in. - * @return the description string. - */ - virtual std::string getDescription() const = 0; - - /** - * Check whether the remote instance with given network address is - * valid to use this security plug-in to authNZ. - * @param remoteAddress - * @return true if this security plugin can be used for remote instance. - */ - virtual bool isValidFor(osiSockAddr const & remoteAddress) const = 0; - - /** - * Create a security session (usually one per transport). - * @param remoteAddress - * @return a new session. - * @throws SecurityException - * - * @warning a Ref. loop is created if the SecuritySession stores a pointer to 'control' - */ - // authentication must be done immediately when connection is established (timeout 3seconds), - // later on authentication process can be repeated - // the server and the client can exchange (arbitrary number) of messages using SecurityPluginControl.sendMessage() - // the process completion must be notified by calling AuthenticationControl.completed() - virtual SecuritySession::shared_pointer createSession( - osiSockAddr const & remoteAddress, - SecurityPluginControl::shared_pointer const & control, - epics::pvData::PVField::shared_pointer const & data) = 0; -}; - - - -class epicsShareClass NoSecurityPlugin : - public SecurityPlugin, - public SecuritySession, - public ChannelSecuritySession, - public std::tr1::enable_shared_from_this { -protected: - NoSecurityPlugin() {} - -public: - POINTER_DEFINITIONS(NoSecurityPlugin); - - static NoSecurityPlugin::shared_pointer INSTANCE; - - virtual ~NoSecurityPlugin() {} - - // optional (can be null) initialization data for the remote party - // client to server - virtual epics::pvData::PVField::shared_pointer initializationData() { - return epics::pvData::PVField::shared_pointer(); - } - - // get parent - virtual std::tr1::shared_ptr getSecurityPlugin() { - return shared_from_this(); - } - - // can be called any time, for any reason - virtual void messageReceived(epics::pvData::PVField::shared_pointer const & data) { - // noop - } - - /// closes this session - virtual void close() { - // noop - } - - // notification to the client on allowed requests (bitSet, a bit per request) - virtual ChannelSecuritySession::shared_pointer createChannelSession(std::string const & /*channelName*/) - { - return shared_from_this(); - } - - /** - * Short, unique name for the plug-in, used to identify the plugin. - * @return the ID. - */ - virtual std::string getId() const { - return "none"; - } - - /** - * Description of the security plug-in. - * @return the description string. - */ - virtual std::string getDescription() const { - return "No security plug-in"; - } - - /** - * Check whether the remote instance with given network address is - * valid to use this security plug-in to authNZ. - * @param remoteAddress - * @return true if this security plugin can be used for remote instance. - */ - virtual bool isValidFor(osiSockAddr const & /*remoteAddress*/) const { - return true; - } - - /** - * Create a security session (usually one per transport). - * @param remoteAddress - * @return a new session. - * @throws SecurityException - */ - // authentication must be done immediately when connection is established (timeout 3seconds), - // later on authentication process can be repeated - // the server and the client can exchange (arbitrary number) of messages using SecurityPluginControl.sendMessage() - // the process completion must be notified by calling AuthenticationControl.completed() - virtual SecuritySession::shared_pointer createSession( - osiSockAddr const & /*remoteAddress*/, - SecurityPluginControl::shared_pointer const & control, - epics::pvData::PVField::shared_pointer const & /*data*/) { - control->authenticationCompleted(epics::pvData::Status::Ok); - return shared_from_this(); - } - - // for every authroizeCreate... a release() must be called - virtual void release(pvAccessID ioid) { - // noop - } - - // bitSet w/ one bit - virtual epics::pvData::Status authorizeCreateChannelProcess( - pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & /*pvRequest*/) { - return epics::pvData::Status::Ok; - } - - virtual epics::pvData::Status authorizeProcess(pvAccessID /*ioid*/) { - return epics::pvData::Status::Ok; - } - - // bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - virtual epics::pvData::Status authorizeCreateChannelGet( - pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*pvRequest*/) { - return epics::pvData::Status::Ok; - } - - virtual epics::pvData::Status authorizeGet(pvAccessID /*ioid*/) { - return epics::pvData::Status::Ok; - } - - // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - virtual epics::pvData::Status authorizeCreateChannelPut( - pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*pvRequest*/) { - return epics::pvData::Status::Ok; - } - - virtual epics::pvData::Status authorizePut( - pvAccessID /*ioid*/, - epics::pvData::PVStructure::shared_pointer const & /*dataToPut*/, - epics::pvData::BitSet::shared_pointer const & /*fieldsToPut*/) { - return epics::pvData::Status::Ok; - } - - // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - // process: bitSet w/ one bit (allowed, not allowed) - virtual epics::pvData::Status authorizeCreateChannelPutGet( - pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*pvRequest*/) { - return epics::pvData::Status::Ok; - } - - virtual epics::pvData::Status authorizePutGet( - pvAccessID /*ioid*/, - epics::pvData::PVStructure::shared_pointer const & /*dataToPut*/, - epics::pvData::BitSet::shared_pointer const & /*fieldsToPut*/) { - return epics::pvData::Status::Ok; - } - - // bitSet w/ one bit - virtual epics::pvData::Status authorizeCreateChannelRPC( - pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*pvRequest*/) { - return epics::pvData::Status::Ok; - } - - // one could authorize per operation basis - virtual epics::pvData::Status authorizeRPC( - pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*arguments*/) { - return epics::pvData::Status::Ok; - } - - // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field - virtual epics::pvData::Status authorizeCreateMonitor( - pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*pvRequest*/) { - return epics::pvData::Status::Ok; - } - - virtual epics::pvData::Status authorizeMonitor(pvAccessID /*ioid*/) { - return epics::pvData::Status::Ok; - } - - // read: bitSet w/ one bit (allowed, not allowed) and rest put/get/set length - virtual epics::pvData::Status authorizeCreateChannelArray( - pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*pvRequest*/) { - return epics::pvData::Status::Ok; - } - - // use authorizeGet - virtual epics::pvData::Status authorizePut( - pvAccessID /*ioid*/, epics::pvData::PVArray::shared_pointer const & /*dataToPut*/) { - return epics::pvData::Status::Ok; - } - - virtual epics::pvData::Status authorizeSetLength(pvAccessID /*ioid*/) { - return epics::pvData::Status::Ok; - } - - // introspection authorization - virtual epics::pvData::Status authorizeGetField(pvAccessID /*ioid*/, std::string const & /*subField*/) { - return epics::pvData::Status::Ok; - } - -}; - -class epicsShareClass CAClientSecurityPlugin : - public NoSecurityPlugin { -protected: - epics::pvData::PVStructure::shared_pointer m_userAndHost; - - CAClientSecurityPlugin(); - - -public: - POINTER_DEFINITIONS(CAClientSecurityPlugin); - - static CAClientSecurityPlugin::shared_pointer INSTANCE; - - virtual epics::pvData::PVField::shared_pointer initializationData() { - return m_userAndHost; - } - - virtual std::string getId() const { - return "ca"; - } - - virtual std::string getDescription() const { - return "CA client security plug-in"; - } -}; - -class epicsShareClass SecurityPluginRegistry +/** A particular authentication exchange. See AuthenticationPlugin::createSession() + * + * @note Must not hold a strong reference to AuthenticationPluginControl + */ +class epicsShareClass AuthenticationSession { - EPICS_NOT_COPYABLE(SecurityPluginRegistry) public: + POINTER_DEFINITIONS(AuthenticationSession); - static SecurityPluginRegistry& instance() - { - static SecurityPluginRegistry thisInstance; - return thisInstance; - } + virtual ~AuthenticationSession(); - typedef std::map > securityPlugins_t; + //! For client plugins only, call to find the payload returned with CONNECTION_VALIDATION. + //! May return NULL. + virtual epics::pvData::PVStructure::const_shared_pointer initializationData() + { return epics::pvData::PVStructure::const_shared_pointer(); } - securityPlugins_t& getClientSecurityPlugins() - { - return m_clientSecurityPlugins; - } + //! Called when an AUTHZ message is recieved from the peer. + //! See AuthenticationPluginControl::sendSecurityPluginMessage(). + //! callee accepts ownership of data, which will not be modified. + virtual void messageReceived(epics::pvData::PVStructure::const_shared_pointer const & data) {} - securityPlugins_t& getServerSecurityPlugins() - { - return m_serverSecurityPlugins; - } + /** For client plugins only. Notification that server has declared the exchange complete. + * @param status Check Status::isSuccess() + * @param peer Final information about pe + */ + virtual void authenticationComplete(const epics::pvData::Status& status) {} +}; - void installClientSecurityPlugin(std::tr1::shared_ptr plugin) - { - m_clientSecurityPlugins[plugin->getId()] = plugin; - LOG(epics::pvAccess::logLevelDebug, "Client security plug-in '%s' installed.", plugin->getId().c_str()); - } +//! Callbacks for use by AuthenticationSession +class epicsShareClass AuthenticationPluginControl +{ +public: + POINTER_DEFINITIONS(AuthenticationPluginControl); + virtual ~AuthenticationPluginControl(); - void installServerSecurityPlugin(std::tr1::shared_ptr plugin) - { - m_serverSecurityPlugins[plugin->getId()] = plugin; - LOG(epics::pvAccess::logLevelDebug, "Server security plug-in '%s' installed.", plugin->getId().c_str()); - } + //! Send AUTHZ to peer with payload. + //! caller gives up ownership of data, which must not be modified. + virtual void sendSecurityPluginMessage(epics::pvData::PVStructure::const_shared_pointer const & data) = 0; + + /** Called by server plugin to indicate the the exchange has completed. + * + * @param status If !status.isSuccess() then the connection will be closed without being used. + * @param peer Partially initialized PeerInfo. See AuthenticationPlugin::createSession(). + * PeerInfo::realm and/or PeerInfo::account will now be considered valid. + * Caller transfers ownership to callee, which may modify. + */ + virtual void authenticationCompleted(const epics::pvData::Status& status, + const std::tr1::shared_ptr& peer) = 0; +}; + +//! Actor through which authentication exchanges are initiated. +class epicsShareClass AuthenticationPlugin +{ +public: + POINTER_DEFINITIONS(AuthenticationPlugin); + virtual ~AuthenticationPlugin(); + + /** Allow this plugin to be advertised to a particular peer. + * + * At this point the PeerInfo has only been partially initialized with + * transport/protocol specific information: PeerInfo::peer, PeerInfo::transport, and PeerInfo::transportVersion. + */ + virtual bool isValidFor(const PeerInfo& peer) const { return true; } + + /** Begin a new session with a peer. + * + * @param peer Partially initialized PeerInfo. See isValidFor(). + * PeerInfo::authority is also set. + * Caller transfers ownership to callee, which may modify. + * @param control callee uses to asynchronously continue, and complete the session. + * @param data Always NULL for client-type plugins. For server-type plugins, + * the result of initializationData() from the peer + */ + virtual std::tr1::shared_ptr createSession( + const std::tr1::shared_ptr& peer, + std::tr1::shared_ptr const & control, + epics::pvData::PVStructure::shared_pointer const & data) = 0; +}; + +/** Registry(s) for plugins + */ +class epicsShareClass AuthenticationRegistry +{ + EPICS_NOT_COPYABLE(AuthenticationRegistry) // would need locking +public: + POINTER_DEFINITIONS(AuthenticationRegistry); private: - SecurityPluginRegistry(); + typedef std::map > map_t; + map_t map; + mutable epicsMutex mutex; +public: + typedef std::vector list_t; - securityPlugins_t m_clientSecurityPlugins; - securityPlugins_t m_serverSecurityPlugins; + //! The client side of the conversation + static AuthenticationRegistry& clients(); + //! The server side of the conversation + static AuthenticationRegistry& servers(); + + AuthenticationRegistry() {} + ~AuthenticationRegistry(); + + //! Save a copy of the current registry in order of increasing priority + void snapshot(list_t& plugmap) const; + + /** @brief Add a new plugin to this registry. + * + @param prio Order in which plugins are considered. highest is preferred. + @param name Name under which this plugin will be known + @param plugin Plugin instance + */ + void add(int prio, const std::string& name, const AuthenticationPlugin::shared_pointer& plugin); + //! Remove an existing entry. Remove true if the entry was actually removed. + bool remove(const AuthenticationPlugin::shared_pointer& plugin); + //! Fetch a single plugin explicitly by name. + //! @returns NULL if no entry for this name is available. + AuthenticationPlugin::shared_pointer lookup(const std::string& name) const; }; } diff --git a/src/remote/pv/serializationHelper.h b/src/remote/pv/serializationHelper.h index 4894c28..dd0b9d0 100644 --- a/src/remote/pv/serializationHelper.h +++ b/src/remote/pv/serializationHelper.h @@ -82,7 +82,7 @@ public: * Serialize optional PVField. * @param buffer data buffer. */ - static void serializeFull(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control, epics::pvData::PVField::shared_pointer const & pvField); + static void serializeFull(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control, const epics::pvData::PVField::const_shared_pointer &pvField); }; diff --git a/src/remote/security.cpp b/src/remote/security.cpp index d0104a8..abb268d 100644 --- a/src/remote/security.cpp +++ b/src/remote/security.cpp @@ -6,51 +6,223 @@ #include +#include +#include +#include +#include + #define epicsExportSharedSymbols #include -using namespace epics::pvData; -using namespace epics::pvAccess; +typedef epicsGuard Guard; -NoSecurityPlugin::shared_pointer NoSecurityPlugin::INSTANCE(new NoSecurityPlugin()); +namespace { +namespace pvd = epics::pvData; +namespace pva = epics::pvAccess; -CAClientSecurityPlugin::shared_pointer CAClientSecurityPlugin::INSTANCE(new CAClientSecurityPlugin()); -CAClientSecurityPlugin::CAClientSecurityPlugin() +pvd::StructureConstPtr userAndHostStructure( + pvd::FieldBuilder::begin()-> + add("user", pvd::pvString)-> + add("host", pvd::pvString)-> + createStructure() +); + +struct SimpleSession : public pva::AuthenticationSession { - StructureConstPtr userAndHostStructure = - getFieldCreate()->createFieldBuilder()-> - add("user", pvString)-> - add("host", pvString)-> - createStructure(); + const pvd::PVStructure::const_shared_pointer initdata; - m_userAndHost = getPVDataCreate()->createPVStructure(userAndHostStructure); + SimpleSession(const pvd::PVStructure::const_shared_pointer& data) :initdata(data) {} + virtual ~SimpleSession() {} - // - // user name - // + virtual epics::pvData::PVStructure::const_shared_pointer initializationData() OVERRIDE FINAL + { return initdata; } +}; - char buffer[256]; +struct AnonPlugin : public pva::AuthenticationPlugin +{ + const bool server; - std::string userName; - if (osiGetUserName(buffer, sizeof(buffer)) == osiGetUserNameSuccess) - userName = buffer; - // TODO more error handling + AnonPlugin(bool server) :server(server) {} + virtual ~AnonPlugin() {} - m_userAndHost->getSubFieldT("user")->put(userName); + virtual std::tr1::shared_ptr createSession( + const std::tr1::shared_ptr& peer, + std::tr1::shared_ptr const & control, + epics::pvData::PVStructure::shared_pointer const & data) OVERRIDE FINAL + { + std::tr1::shared_ptr 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; + } +}; - // - // host name - // +struct CAPlugin : public pva::AuthenticationPlugin +{ + const bool server; + // fully const after ctor + const pvd::PVStructurePtr user; - std::string hostName; - if (gethostname(buffer, sizeof(buffer)) == 0) - hostName = buffer; - // TODO more error handling + CAPlugin(bool server) + :server(server) + ,user(userAndHostStructure->build()) + { + std::vector buffer(256u); + if(osiGetUserName(&buffer[0], buffer.size()) != osiGetUserNameSuccess) + throw std::runtime_error("Unable to determine user account name"); - m_userAndHost->getSubFieldT("host")->put(buffer); + buffer[buffer.size()-1] = '\0'; + user->getSubFieldT("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("host")->put(&buffer[0]); + } + virtual ~CAPlugin() {} + + virtual std::tr1::shared_ptr createSession( + const std::tr1::shared_ptr& peer, + std::tr1::shared_ptr const & control, + epics::pvData::PVStructure::shared_pointer const & data) OVERRIDE FINAL + { + std::tr1::shared_ptr sess(new SimpleSession(user)); // no init data + if(server) { + peer->identified = true; + peer->account = data->getSubFieldT("user")->get(); + peer->aux = pvd::getPVDataCreate()->createPVStructure(data); // clone to ensure it won't be modified + control->authenticationCompleted(pvd::Status::Ok, peer); + } + return sess; + } +}; + +} // 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() {} + +namespace { +struct authGbl_t { + mutable epicsMutex mutex; + AuthenticationRegistry servers, clients; +} *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); + } + + 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(); +} + + void AuthNZHandler::handleResponse(osiSockAddr* responseFrom, Transport::shared_pointer const & transport, @@ -61,15 +233,17 @@ void AuthNZHandler::handleResponse(osiSockAddr* responseFrom, { ResponseHandler::handleResponse(responseFrom, transport, version, command, payloadSize, payloadBuffer); - epics::pvData::PVField::shared_pointer data = - SerializationHelper::deserializeFull(payloadBuffer, transport.get()); + 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(raw); + } else { + // was originally possible, but never used + } + } transport->authNZMessage(data); } -SecurityPluginRegistry::SecurityPluginRegistry() { - // install CA client security plugin by default - installClientSecurityPlugin(CAClientSecurityPlugin::INSTANCE); -} - - +}} // namespace epics::pvAccess diff --git a/src/remote/serializationHelper.cpp b/src/remote/serializationHelper.cpp index e1bad43..63a9e13 100644 --- a/src/remote/serializationHelper.cpp +++ b/src/remote/serializationHelper.cpp @@ -81,7 +81,7 @@ void SerializationHelper::serializeStructureFull(ByteBuffer* buffer, Serializabl serializeFull(buffer, control, pvStructure); } -void SerializationHelper::serializeFull(ByteBuffer* buffer, SerializableControl* control, PVField::shared_pointer const & pvField) +void SerializationHelper::serializeFull(ByteBuffer* buffer, SerializableControl* control, PVField::const_shared_pointer const & pvField) { if (!pvField) { diff --git a/src/remoteClient/clientContextImpl.cpp b/src/remoteClient/clientContextImpl.cpp index 3a4cc70..25e4e5b 100644 --- a/src/remoteClient/clientContextImpl.cpp +++ b/src/remoteClient/clientContextImpl.cpp @@ -4164,9 +4164,6 @@ private: m_channelSearchManager.reset(new ChannelSearchManager(thisPointer)); - // preinitialize security plugins - SecurityPluginRegistry::instance(); - // TODO put memory barrier here... (if not already called within a lock?) // setup UDP transport @@ -4448,11 +4445,6 @@ private: } } - const securityPlugins_t& getSecurityPlugins() OVERRIDE FINAL - { - return SecurityPluginRegistry::instance().getClientSecurityPlugins(); - } - /** * Get channel search manager. * @return channel search manager. diff --git a/src/server/pv/responseHandlers.h b/src/server/pv/responseHandlers.h index b8f62b0..5e6ee74 100644 --- a/src/server/pv/responseHandlers.h +++ b/src/server/pv/responseHandlers.h @@ -201,14 +201,15 @@ public: protected: ServerChannelRequesterImpl(Transport::shared_pointer const & transport, const std::string channelName, - const pvAccessID cid, ChannelSecuritySession::shared_pointer const & css); + const pvAccessID cid); public: virtual ~ServerChannelRequesterImpl() {} static ChannelRequester::shared_pointer create(ChannelProvider::shared_pointer const & provider, Transport::shared_pointer const & transport, const std::string channelName, - const pvAccessID cid, ChannelSecuritySession::shared_pointer const & css); + const pvAccessID cid); virtual void channelCreated(const epics::pvData::Status& status, Channel::shared_pointer const & channel) OVERRIDE FINAL; virtual void channelStateChange(Channel::shared_pointer const & c, const Channel::ConnectionState isConnected) OVERRIDE FINAL; + virtual std::tr1::shared_ptr getPeerInfo() OVERRIDE FINAL; virtual std::string getRequesterName() OVERRIDE FINAL; virtual void message(std::string const & message, epics::pvData::MessageType messageType) OVERRIDE FINAL; virtual void send(epics::pvData::ByteBuffer* buffer, TransportSendControl* control) OVERRIDE FINAL; @@ -217,7 +218,6 @@ private: std::tr1::weak_ptr _transport; const std::string _channelName; const pvAccessID _cid; - ChannelSecuritySession::shared_pointer const & _css; epics::pvData::Status _status; epics::pvData::Mutex _mutex; }; diff --git a/src/server/pv/serverChannelImpl.h b/src/server/pv/serverChannelImpl.h index f74c4b5..87d03c7 100644 --- a/src/server/pv/serverChannelImpl.h +++ b/src/server/pv/serverChannelImpl.h @@ -33,8 +33,7 @@ public: */ ServerChannel(Channel::shared_pointer const & channel, const ChannelRequester::shared_pointer& requester, - pvAccessID cid, pvAccessID sid, - ChannelSecuritySession::shared_pointer const & css); + pvAccessID cid, pvAccessID sid); ~ServerChannel(); const Channel::shared_pointer& getChannel() const { return _channel; } @@ -43,9 +42,6 @@ public: pvAccessID getSID() const { return _sid; } - ChannelSecuritySession::shared_pointer getChannelSecuritySession() const - { return _channelSecuritySession; } - void registerRequest(pvAccessID id, const std::tr1::shared_ptr& request); void unregisterRequest(pvAccessID id); @@ -77,8 +73,6 @@ private: bool _destroyed; mutable epics::pvData::Mutex _mutex; - - const ChannelSecuritySession::shared_pointer _channelSecuritySession; }; } diff --git a/src/server/pv/serverContextImpl.h b/src/server/pv/serverContextImpl.h index 40a2536..ff07e93 100644 --- a/src/server/pv/serverContextImpl.h +++ b/src/server/pv/serverContextImpl.h @@ -51,7 +51,6 @@ public: Transport::shared_pointer getSearchTransport() OVERRIDE FINAL; Configuration::const_shared_pointer getConfiguration() OVERRIDE FINAL; TransportRegistry* getTransportRegistry() OVERRIDE FINAL; - const securityPlugins_t& getSecurityPlugins() OVERRIDE FINAL; virtual void newServerDetected() OVERRIDE FINAL; diff --git a/src/server/responseHandlers.cpp b/src/server/responseHandlers.cpp index a17b75d..a5fc1d4 100644 --- a/src/server/responseHandlers.cpp +++ b/src/server/responseHandlers.cpp @@ -194,15 +194,30 @@ void ServerConnectionValidationHandler::handleResponse( std::string securityPluginName = SerializeHelper::deserializeString(payloadBuffer, transport.get()); // optional authNZ plug-in initialization data - PVField::shared_pointer data; - if (payloadBuffer->getRemaining()) - data = SerializationHelper::deserializeFull(payloadBuffer, transport.get()); + PVStructure::shared_pointer data; + if (payloadBuffer->getRemaining()) { + PVField::shared_pointer raw(SerializationHelper::deserializeFull(payloadBuffer, transport.get())); + if(raw && raw->getField()->getType()==structure) { + data = std::tr1::static_pointer_cast(raw); + } else { + // was originally allowed, but never used + } + } detail::BlockingServerTCPTransportCodec* casTransport(static_cast(transport.get())); //TODO: simplify byzantine class heirarchy... assert(casTransport); - casTransport->authNZInitialize(securityPluginName, data); + try { + casTransport->authNZInitialize(securityPluginName, data); + }catch(std::exception& e){ + if (IS_LOGGABLE(logLevelDebug)) + { + LOG(logLevelDebug, "Security plug-in '%s' failed to create a session for PVA client: %s.", securityPluginName.c_str(), casTransport->getRemoteName().c_str()); + } + casTransport->verified(pvData::Status::error(e.what())); + throw; + } } @@ -732,30 +747,13 @@ void ServerCreateChannelHandler::handleResponse(osiSockAddr* responseFrom, return; } - SecuritySession::shared_pointer securitySession = transport->getSecuritySession(); - ChannelSecuritySession::shared_pointer css; - try { - css = securitySession->createChannelSession(channelName); - if (!css) - throw SecurityException("null channelSecuritySession"); - } catch (SecurityException& se) { - // TODO use std::make_shared - std::tr1::shared_ptr tp(new ServerChannelRequesterImpl(transport, channelName, cid, css)); - ChannelRequester::shared_pointer cr = tp; - - Status asStatus(Status::STATUSTYPE_ERROR, - string("Insufficient rights to create a channel: ") + se.what()); - cr->channelCreated(asStatus, Channel::shared_pointer()); - return; - } - if (channelName == SERVER_CHANNEL_NAME) { // TODO singleton!!! ServerRPCService::shared_pointer serverRPCService(new ServerRPCService(_context)); // TODO use std::make_shared - std::tr1::shared_ptr tp(new ServerChannelRequesterImpl(transport, channelName, cid, css)); + std::tr1::shared_ptr tp(new ServerChannelRequesterImpl(transport, channelName, cid)); ChannelRequester::shared_pointer cr = tp; Channel::shared_pointer serverChannel = createRPCChannel(ChannelProvider::shared_pointer(), channelName, cr, serverRPCService); cr->channelCreated(Status::Ok, serverChannel); @@ -766,7 +764,7 @@ void ServerCreateChannelHandler::handleResponse(osiSockAddr* responseFrom, ServerContextImpl::s_channelNameToProvider_t::const_iterator it; if (_providers.size() == 1) - ServerChannelRequesterImpl::create(_providers[0], transport, channelName, cid, css); + ServerChannelRequesterImpl::create(_providers[0], transport, channelName, cid); else { ChannelProvider::shared_pointer prov; { @@ -775,7 +773,7 @@ void ServerCreateChannelHandler::handleResponse(osiSockAddr* responseFrom, prov = it->second.lock(); } if(prov) - ServerChannelRequesterImpl::create(prov, transport, channelName, cid, css); + ServerChannelRequesterImpl::create(prov, transport, channelName, cid); } } } @@ -786,12 +784,11 @@ void ServerCreateChannelHandler::disconnect(Transport::shared_pointer const & tr } ServerChannelRequesterImpl::ServerChannelRequesterImpl(const Transport::shared_pointer &transport, - const string channelName, const pvAccessID cid, ChannelSecuritySession::shared_pointer const & css) : + const string channelName, const pvAccessID cid) : _serverChannel(), _transport(std::tr1::static_pointer_cast(transport)), _channelName(channelName), _cid(cid), - _css(css), _status(), _mutex() { @@ -799,10 +796,10 @@ ServerChannelRequesterImpl::ServerChannelRequesterImpl(const Transport::shared_p ChannelRequester::shared_pointer ServerChannelRequesterImpl::create( ChannelProvider::shared_pointer const & provider, Transport::shared_pointer const & transport, - const string channelName, const pvAccessID cid, ChannelSecuritySession::shared_pointer const & css) + const string channelName, const pvAccessID cid) { // TODO use std::make_shared - std::tr1::shared_ptr tp(new ServerChannelRequesterImpl(transport, channelName, cid, css)); + std::tr1::shared_ptr tp(new ServerChannelRequesterImpl(transport, channelName, cid)); ChannelRequester::shared_pointer cr = tp; // TODO exception guard and report error back provider->createChannel(channelName, cr, transport->getPriority()); @@ -824,7 +821,7 @@ void ServerChannelRequesterImpl::channelCreated(const Status& status, Channel::s pvAccessID sid = transport->preallocateChannelSID(); try { - serverChannel.reset(new ServerChannel(channel, shared_from_this(), _cid, sid, _css)); + serverChannel.reset(new ServerChannel(channel, shared_from_this(), _cid, sid)); // ack allocation and register transport->registerChannel(sid, serverChannel); @@ -836,11 +833,6 @@ void ServerChannelRequesterImpl::channelCreated(const Status& status, Channel::s throw; } } - else - { - if (_css) - _css->close(); - } { @@ -861,9 +853,6 @@ void ServerChannelRequesterImpl::channelCreated(const Status& status, Channel::s } TransportSender::shared_pointer thisSender = shared_from_this(); transport->enqueueSendRequest(thisSender); - // TODO make sure that serverChannel gets destroyed - if (_css) - _css->close(); } catch (...) { @@ -874,9 +863,6 @@ void ServerChannelRequesterImpl::channelCreated(const Status& status, Channel::s } TransportSender::shared_pointer thisSender = shared_from_this(); transport->enqueueSendRequest(thisSender); - // TODO make sure that serverChannel gets destroyed - if (_css) - _css->close(); } } } @@ -909,6 +895,17 @@ void ServerChannelRequesterImpl::channelStateChange(Channel::shared_pointer cons } } +std::tr1::shared_ptr ServerChannelRequesterImpl::getPeerInfo() +{ + if(detail::BlockingServerTCPTransportCodec::shared_pointer transport = _transport.lock()) { + epicsGuard G(transport->_mutex); + return transport->_peerInfo; + + } else { + return std::tr1::shared_ptr(); + } +} + string ServerChannelRequesterImpl::getRequesterName() { detail::BlockingServerTCPTransportCodec::shared_pointer transport = _transport.lock(); diff --git a/src/server/serverChannelImpl.cpp b/src/server/serverChannelImpl.cpp index f98b77c..1402989 100644 --- a/src/server/serverChannelImpl.cpp +++ b/src/server/serverChannelImpl.cpp @@ -18,14 +18,12 @@ size_t ServerChannel::num_instances; ServerChannel::ServerChannel(Channel::shared_pointer const & channel, const ChannelRequester::shared_pointer &requester, - pvAccessID cid, pvAccessID sid, - ChannelSecuritySession::shared_pointer const & css): + pvAccessID cid, pvAccessID sid): _channel(channel), _requester(requester), _cid(cid), _sid(sid), - _destroyed(false), - _channelSecuritySession(css) + _destroyed(false) { REFTRACE_INCREMENT(num_instances); if (!channel.get()) @@ -76,10 +74,6 @@ void ServerChannel::destroy() // removal via unregisterRequest() during iteration _requests.swap(reqs); - // close channel security session - // TODO try catch - _channelSecuritySession->close(); - // ... and the channel // TODO try catch _channel->destroy(); diff --git a/src/server/serverContext.cpp b/src/server/serverContext.cpp index f10c338..447dc1c 100644 --- a/src/server/serverContext.cpp +++ b/src/server/serverContext.cpp @@ -533,12 +533,6 @@ epicsTimeStamp& ServerContextImpl::getStartTime() return _startTime; } - -const Context::securityPlugins_t& ServerContextImpl::getSecurityPlugins() -{ - return SecurityPluginRegistry::instance().getServerSecurityPlugins(); -} - ServerContext::shared_pointer startPVAServer(std::string const & providerNames, int timeToRun, bool runInSeparateThread, bool printInfo) { ServerContext::shared_pointer ret(ServerContext::create(ServerContext::Config() diff --git a/testApp/remote/testCodec.cpp b/testApp/remote/testCodec.cpp index 0ad7959..229d7c6 100644 --- a/testApp/remote/testCodec.cpp +++ b/testApp/remote/testCodec.cpp @@ -396,13 +396,7 @@ public: void aliveNotification() {} - void authNZMessage(epics::pvData::PVField::shared_pointer const & data) {} - - virtual std::tr1::shared_ptr getSecuritySession() const - { - return std::tr1::shared_ptr(); - } - + void authNZMessage(epics::pvData::PVStructure::shared_pointer const & data) {} bool isClosed() {