Andrew Stitcher wrote:
On Sun, 2008-04-20 at 01:48 -0400, Joshua Kramer wrote:
If you have separate listening connections which are either wholly SSL
or wholly not it makes more sense (to my mind) to keep the
implementations separate (but possible to use inheritance if that makes
sense) and to have a new SSL specific Acceptor to be the factory for the
new SSL connections.
Can you describe, a bit more, the structure of the Acceptor? It appears
(from the file TCPIOPlugin.cpp, line 72) that the Acceptor starts
listening before we tell it what port we want it to listen on. (It
actually appears that there is no 'Acceptor' that runs as itself - the
Acceptor class is used to derive the AsynchIOAcceptor.
Acceptor is an abstract class which does not have any instances of its
own - it's purpose is to define an interface for starting connections.
At this point the abstract class is misnamed as it is also used for
making connections from the broker as well as accepting them - so this
interface may well be renamed to something better fitting sometime soon.
The main broker object has a list of Acceptor objects and it uses all of
the Acceptor to accept connections [in Broker::accept()] this happens
currently by calling by the run() method of acceptor [probably should be
called accept() now].
The parameters for the acceptor have already been set by the Acceptor's
constructor.
The Acceptor is constructed using the plugin architecture of the
broker.
Where do we set config information, or at least pass it on to the Acceptor
and/or Socket objects? This is important, because for an SSL acceptor, we
need to set and get:
-Permitted and preferred Cipher Algorithms
-Do we ask for client certificate?
-Do we require client certificate?
-Location of key and certificate databases
-One of US, French, or International nationality settings
-Callback functions to get passwords for certificates
-Port
-Host
You can use the option processing of the broker to provide your SSLAcceptor
with the parameters you require. See TCPIOPlugin::initialize() for how we get
the port to listen to. In this case it seems that you'll want to add your own
commend line options to be processed you can do this by implementing a
getOptions() member in your xxPlugin - see ClusterPlugin.cpp for how this
works. The options infrastructure uses the boost options processor and is quite
comprehensive.
So far, I've added SSLAcceptor, SSLAsynchIO, and SSLSocket classes. Soon
after I have answers to the above I should have some code for you to
review.
I'd guess that you don't really need to reimplement the AsynchIO code
specially for SSLSockets - if they subclass Sockets correctly then the
AsynchIO code should work with them as is (this may of course be subject
to some bug fixes!)
You will however need to implement your own SSLIOPlugin.cpp along the
lines of TCPIOPlugin.cpp to register the SSL protocol capability.
Looking forward to seeing your patches
Andrew
Andrew,
I've attached a starting point for SSLIOPlugin.cpp. So far I have the
init routines done, and I'm working on the socket routines next.
Let me know if I'm on the right track!
Thanks,
-J
/*
*
* 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.
*
*/
// TODO: Determine if we ever read/write in single thread. We need to
// set SSL_ENABLE_FDX if we do read/write in single thread.
#include "SSLAcceptor.h"
#include "SSLAsynchIOHandler.h"
#include "SSLAsynchIO.h"
#include "qpid/Plugin.h"
#include "qpid/broker/Broker.h"
#include "qpid/log/Statement.h"
#include "qpid/Options.h"
#include <boost/bind.hpp>
#include <memory>
namespace qpid {
namespace sys {
using namespace std;
struct SSLOptions : public Options {
string ssl_locale_policy;
string ssl_enabled;
string ssl_request_certificate;
string ssl_require_certificate;
string ssl_cert_directory;
string ssl_enable_ssl2;
string ssl_enable_ssl3;
SSLOptions() : Options("SSL Options") {
addOptions()
("ssl-enabled", optValue(ssl_enabled, "SSL-ENABLED"),
"SSL is Enabled if set to yes.\n")
("ssl-locale-policy", optValue(ssl_locale_policy,
"SSL-LOCALE-POLICY"), "Locale Policy; one of the following:\n"
"DomesticPolicy | ExportPolicy | FrancePolicy\n")
("ssl-request-certificate", optValue(ssl_request_certificate,
"SSL-REQUEST-CERTIFICATE"), "Server requests certificate from clients if set to
yes.\n");
("ssl-require-certificate", optValue(ssl_require_certificate,
"SSL-REQUIRE-CERTIFICATE"), "Server requires certificate from clients if set to
yes.\nMust also set ssl-request-certificate to yes.\n")
("ssl-cert-directory", optValue(ssl_cert_directory,
"SSL-CERT-DIRECTORY"), "Directory containing certN.db, keyN.db, and
secman.db\n")
// JPK TODO: Enable below options.
/*("ssl-enable-ssl2", optValue(ssl_enable_ssl2, "SSL-ENABLE-SSL2"),
"SSL2 enabled if set to yes.\n")
("ssl-enable-ssl3", optValue(ssl_enable_ssl3, "SSL-ENABLE-SSL3"),
"SSL3 enabled if set to yes.\n")*/
}
Url getUrl(uint16_t port) const {
if (url.empty()) return Url::getIpAddressesUrl(port);
return Url(url);
}
};
class SSLAsynchIOAcceptor : public SSLAcceptor {
SSLSocket listener;
const uint16_t listeningPort;
std::auto_ptr<AsynchAcceptor> acceptor;
SSLOptions options;
std::string strYes = "yes";
std::string strDir;
Options* getOptions() { return &options; }
public:
AsynchIOAcceptor(int16_t port, int backlog);
void run(Poller::shared_ptr, ConnectionCodec::Factory*);
void connect(Poller::shared_ptr, const std::string& host, int16_t port,
ConnectionCodec::Factory*);
uint16_t getPort() const;
std::string getHost() const;
private:
void accepted(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*);
};
// Static instance to initialise plugin
static class SSLIOPlugin : public Plugin {
void earlyInitialize(Target&) {
SECStatus ssSecStatus;
std::map<std::string, LocalePolicies> mLocPolTable;
string strLocalePolicy;
// JPK: Do SSL Init stuff here (loading of certificates, etc.)
if (options.ssl_enabled != strYes)
return;
PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
// JPK: TODO: We really ought to call PK11_SetPasswordFunc(func)
// to do proper password management for certs that require it.
// Until we do so, library will prompt
strDir = options.ssl_cert_dir;
ssSecStatus = NSS_Init(strDir.c_str());
if (ssSecStatus != SECSuccess)
{
throw Exception("Error in NSS Init!");
}
strLocalePolicy = options.ssl_locale_policy;
mLocPolTable["DomesticPolicy"] = DOMESTIC_POLICY;
mLocPolTable["ExportPolicy"] = EXPORT_POLICY;
mLocPolTable["FrancePolicy"] = FRANCE_POLICY;
switch (mLocPolTable[strLocalePolicy])
{
case DOMESTIC_POLICY:
ssSecStatus = NSS_SetDomesticPolicy();
break;
case EXPORT_POLICY:
ssSecStatus = NSS_SetExportPolicy();
break;
case FRANCE_POLICY:
ssSecStatus = NSS_SetFrancePolicy();
break;
}
if (ssSecStatus != SECSuccess)
{
throw Exception("Error in NSS Set Locale Policy!");
}
// At this point, we should be good to go.
return;
}
void initialize(Target& target) {
broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
// Only provide to a Broker
if (broker) {
const broker::Broker::Options& opts = broker->getOptions();
Acceptor::shared_ptr acceptor(new AsynchIOAcceptor(opts.port,
opts.connectionBacklog));
QPID_LOG(info, "Listening on TCP port " << acceptor->getPort());
broker->registerAccepter(acceptor);
}
}
} acceptor;
AsynchIOAcceptor::AsynchIOAcceptor(int16_t port, int backlog) :
listeningPort(listener.listen(port, backlog))
{}
void AsynchIOAcceptor::accepted(Poller::shared_ptr poller, const Socket& s,
ConnectionCodec::Factory* f) {
AsynchIOHandler* async = new AsynchIOHandler(s.getPeerAddress(), f);
AsynchIO* aio = new AsynchIO(s,
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));
async->init(aio, 4);
aio->start(poller);
}
uint16_t AsynchIOAcceptor::getPort() const {
return listeningPort; // Immutable no need for lock.
}
std::string AsynchIOAcceptor::getHost() const {
return listener.getSockname();
}
void AsynchIOAcceptor::run(Poller::shared_ptr poller, ConnectionCodec::Factory*
fact) {
acceptor.reset(
new AsynchAcceptor(listener,
boost::bind(&AsynchIOAcceptor::accepted, this,
poller, _1, fact)));
acceptor->start(poller);
}
void AsynchIOAcceptor::connect(
Poller::shared_ptr poller,
const std::string& host, int16_t port,
ConnectionCodec::Factory* f)
{
Socket* socket = new Socket();//Should be deleted by handle when socket
closes
socket->connect(host, port);
AsynchIOHandler* async = new AsynchIOHandler(socket->getPeerAddress(), f);
async->setClient();
AsynchIO* aio = new AsynchIO(*socket,
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));
async->init(aio, 4);
aio->start(poller);
}
}} // namespace qpid::sys