blob: 0ae4d8356d4fc52c4b7bf0cba76766e5b0236a12 [file]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
#include "qpid/sys/TransportFactory.h"
#include "qpid/sys/SocketTransport.h"
#include "qpid/Plugin.h"
#include "qpid/broker/Broker.h"
#include "qpid/log/Statement.h"
#include "qpid/sys/AsynchIOHandler.h"
#include "qpid/sys/ConnectionCodec.h"
#include "qpid/sys/Socket.h"
#include "qpid/sys/SocketAddress.h"
#include "qpid/sys/SystemInfo.h"
#include "qpid/sys/windows/SslAsynchIO.h"
#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <memory>
// security.h needs to see this to distinguish from kernel use.
#define SECURITY_WIN32
#include <security.h>
#include <Schnlsp.h>
#undef SECURITY_WIN32
namespace qpid {
namespace sys {
class Timer;
namespace windows {
struct SslServerOptions : qpid::Options
{
std::string certStore;
std::string certStoreLocation;
std::string certName;
uint16_t port;
bool clientAuth;
SslServerOptions() : qpid::Options("SSL Options"),
certStore("My"),
certStoreLocation("CurrentUser"),
certName("localhost"),
port(5671),
clientAuth(false)
{
qpid::Address me;
if (qpid::sys::SystemInfo::getLocalHostname(me))
certName = me.host;
addOptions()
("ssl-cert-store", optValue(certStore, "NAME"), "Local store name from which to obtain certificate")
("ssl-cert-store-location", optValue(certStoreLocation, "NAME"),
"Local store name location for certificates ( CurrentUser | LocalMachine | CurrentService )")
("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use")
("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections")
("ssl-require-client-authentication", optValue(clientAuth),
"Forces clients to authenticate in order to establish an SSL connection");
}
};
class SslProtocolFactory : public qpid::sys::SocketAcceptor, public qpid::sys::TransportConnector {
Timer& brokerTimer;
uint32_t maxNegotiateTime;
const bool tcpNoDelay;
std::string brokerHost;
const bool clientAuthSelected;
std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor;
ConnectFailedCallback connectFailedCallback;
CredHandle credHandle;
public:
SslProtocolFactory(const qpid::broker::Broker& broker, const SslServerOptions&, Timer& timer);
~SslProtocolFactory();
void connect(sys::Poller::shared_ptr, const std::string& name, const std::string& host, const std::string& port,
sys::ConnectionCodec::Factory*,
ConnectFailedCallback failed);
private:
void connectFailed(const qpid::sys::Socket&,
int err,
const std::string& msg);
void establishedIncoming(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*);
void establishedOutgoing(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*, std::string& );
void establishedCommon(sys::Poller::shared_ptr, sys::AsynchIOHandler*, sys::AsynchIO*, const qpid::sys::Socket&);
};
// Static instance to initialise plugin
static struct SslPlugin : public Plugin {
SslServerOptions options;
Options* getOptions() { return &options; }
void earlyInitialize(Target&) {
}
void initialize(Target& target) {
broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
// Only provide to a Broker
if (broker) {
try {
boost::shared_ptr<SslProtocolFactory> protocol(new SslProtocolFactory(*broker, options, broker->getTimer()));
uint16_t port =
protocol->listen(broker->getListenInterfaces(),
options.port, broker->getConnectionBacklog(),
&createSocket);
QPID_LOG(notice, "Listening for SSL connections on TCP port " << port);
broker->registerTransport("ssl", protocol, protocol, port);
} catch (const std::exception& e) {
QPID_LOG(error, "Failed to initialise SSL listener: " << e.what());
}
}
}
} sslPlugin;
SslProtocolFactory::SslProtocolFactory(const qpid::broker::Broker& broker, const SslServerOptions& options, Timer& timer)
: SocketAcceptor(broker.getTcpNoDelay(), false, broker.getMaxNegotiateTime(), timer,
boost::bind(&SslProtocolFactory::establishedIncoming, this, _1, _2, _3)),
brokerTimer(timer),
maxNegotiateTime(broker.getMaxNegotiateTime()),
tcpNoDelay(broker.getTcpNoDelay()),
clientAuthSelected(options.clientAuth) {
// Make sure that certificate store is good before listening to sockets
// to avoid having open and listening sockets when there is no cert store
SecInvalidateHandle(&credHandle);
// Get the certificate for this server.
DWORD flags = 0;
std::string certStoreLocation = options.certStoreLocation;
std::transform(certStoreLocation.begin(), certStoreLocation.end(), certStoreLocation.begin(), ::tolower);
if (certStoreLocation == "currentuser") {
flags = CERT_SYSTEM_STORE_CURRENT_USER;
} else if (certStoreLocation == "localmachine") {
flags = CERT_SYSTEM_STORE_LOCAL_MACHINE;
} else if (certStoreLocation == "currentservice") {
flags = CERT_SYSTEM_STORE_CURRENT_SERVICE;
} else {
QPID_LOG(error, "Unrecognised SSL certificate store location: " << options.certStoreLocation
<< " - Using default location");
}
HCERTSTORE certStoreHandle;
certStoreHandle = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A,
X509_ASN_ENCODING,
0,
flags |
CERT_STORE_READONLY_FLAG,
options.certStore.c_str());
if (!certStoreHandle)
throw qpid::Exception(QPID_MSG("Opening store " << options.certStore << " " << qpid::sys::strError(GetLastError())));
PCCERT_CONTEXT certContext;
certContext = ::CertFindCertificateInStore(certStoreHandle,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR_A,
options.certName.c_str(),
NULL);
if (certContext == NULL) {
int err = ::GetLastError();
::CertCloseStore(certStoreHandle, 0);
throw qpid::Exception(QPID_MSG("Locating certificate " << options.certName << " in store " << options.certStore << " " << qpid::sys::strError(GetLastError())));
throw QPID_WINDOWS_ERROR(err);
}
SCHANNEL_CRED cred;
memset(&cred, 0, sizeof(cred));
cred.dwVersion = SCHANNEL_CRED_VERSION;
cred.cCreds = 1;
cred.paCred = &certContext;
SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL,
UNISP_NAME,
SECPKG_CRED_INBOUND,
NULL,
&cred,
NULL,
NULL,
&credHandle,
NULL);
if (status != SEC_E_OK)
throw QPID_WINDOWS_ERROR(status);
::CertFreeCertificateContext(certContext);
::CertCloseStore(certStoreHandle, 0);
}
SslProtocolFactory::~SslProtocolFactory() {
::FreeCredentialsHandle(&credHandle);
}
void SslProtocolFactory::connectFailed(const qpid::sys::Socket&,
int err,
const std::string& msg) {
if (connectFailedCallback)
connectFailedCallback(err, msg);
}
void SslProtocolFactory::establishedIncoming(sys::Poller::shared_ptr poller,
const qpid::sys::Socket& s,
sys::ConnectionCodec::Factory* f) {
sys::AsynchIOHandler* async = new sys::AsynchIOHandler(s.getFullAddress(), f, false, false);
sys::AsynchIO *aio =
new qpid::sys::windows::ServerSslAsynchIO(
clientAuthSelected,
s,
credHandle,
boost::bind(&AsynchIOHandler::readbuff, async, _1, _2),
boost::bind(&AsynchIOHandler::eof, async, _1),
boost::bind(&AsynchIOHandler::disconnect, async, _1),
boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2),
boost::bind(&AsynchIOHandler::nobuffs, async, _1),
boost::bind(&AsynchIOHandler::idle, async, _1));
establishedCommon(poller, async, aio, s);
}
void SslProtocolFactory::establishedOutgoing(sys::Poller::shared_ptr poller,
const qpid::sys::Socket& s,
sys::ConnectionCodec::Factory* f,
std::string& name) {
sys::AsynchIOHandler* async = new sys::AsynchIOHandler(name, f, true, false);
sys::AsynchIO *aio =
new qpid::sys::windows::ClientSslAsynchIO(
brokerHost,
s,
credHandle,
boost::bind(&AsynchIOHandler::readbuff, async, _1, _2),
boost::bind(&AsynchIOHandler::eof, async, _1),
boost::bind(&AsynchIOHandler::disconnect, async, _1),
boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2),
boost::bind(&AsynchIOHandler::nobuffs, async, _1),
boost::bind(&AsynchIOHandler::idle, async, _1));
establishedCommon(poller, async, aio, s);
}
void SslProtocolFactory::establishedCommon(sys::Poller::shared_ptr poller,
sys::AsynchIOHandler* async,
sys::AsynchIO* aio,
const qpid::sys::Socket& s) {
if (tcpNoDelay) {
s.setTcpNoDelay();
QPID_LOG(debug, "Set TCP_NODELAY on connection to " << s.getPeerAddress());
}
async->init(aio, brokerTimer, maxNegotiateTime);
aio->start(poller);
}
void SslProtocolFactory::connect(sys::Poller::shared_ptr poller,
const std::string& name,
const std::string& host,
const std::string& port,
sys::ConnectionCodec::Factory* fact,
ConnectFailedCallback failed)
{
SCHANNEL_CRED cred;
memset(&cred, 0, sizeof(cred));
cred.dwVersion = SCHANNEL_CRED_VERSION;
SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL,
UNISP_NAME,
SECPKG_CRED_OUTBOUND,
NULL,
&cred,
NULL,
NULL,
&credHandle,
NULL);
if (status != SEC_E_OK)
throw QPID_WINDOWS_ERROR(status);
brokerHost = host;
// Note that the following logic does not cause a memory leak.
// The allocated Socket is freed either by the AsynchConnector
// upon connection failure or by the AsynchIO upon connection
// shutdown. The allocated AsynchConnector frees itself when it
// is no longer needed.
qpid::sys::Socket* socket = createSocket();
connectFailedCallback = failed;
AsynchConnector::create(*socket,
host,
port,
boost::bind(&SslProtocolFactory::establishedOutgoing,
this, poller, _1, fact, name),
boost::bind(&SslProtocolFactory::connectFailed,
this, _1, _2, _3));
}
}}}