Hello Everybody,
Just wanted to provide a quick update on the SSL support for the C++
broker. Things are not progressing as quickly as had hoped, but I am
making progress. My goal is to have the workhorse, SSLSocket.cpp, done
this week. The latest version is attached if someone would like to review
it.
Thanks,
-Josh
--
-----
http://www.globalherald.net/jb01
GlobalHerald.NET, the Smarter Social Network! (tm)
/*
*
* 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.
*
*/
// Initial creation: 19-Apr-2008 Joshua Kramer - [EMAIL PROTECTED]
#include "qpid/sys/SSLSocket.h"
#include "check.h"
#include "PrivatePosix.h"
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cstdlib>
#include <string.h>
#include <boost/format.hpp>
// NSS SSL Includes
#include <nspr.h>
#include <prio.h>
#include <plgetopt.h>
#include <prerror.h>
#include <prnetdb.h>
#include <pk11func.h>
#include <secitem.h>
#include <ssl.h>
#include <certt.h>
#include <nss.h>
#include <secder.h>
#include <key.h>
#include <sslproto.h>
static bool _sys_HAS_RUN_NSS_INIT = false;
namespace qpid {
namespace sys {
namespace {
std::string getName(PRFileDesc *fd, bool local, bool includeService = false)
{
// JPK: TODO: How do we take the 'fd' given by input parameters, and
// map that to the *prfdk PR File Descriptor pointer? Fortunately,
// all getName calls appear to come from within SSLSocket.cpp, so
// it shouldn't be hard to patch.
// JPK: TODO: Add support for IPV6
PRNetAddr name;
PRStatus result;
::socklen_t namelen = sizeof(name);
if (local) {
result = PR_GetSockame(fd, &name);
} else {
result = PR_GetPeerName(fd, &name);
}
if (result == PR_FAILURE) {
// TODO: PR_GetErrorText, GetErrorTextLength, etc. here
}
// QPID_POSIX_CHECK(result);
// JPK: At this point, the 'raw' member of the 'name' will have the
// same structure as sockaddr, so we can use it below:
char servName[NI_MAXSERV];
char dispName[NI_MAXHOST];
if (includeService) {
if (int rc=::getnameinfo((::sockaddr*)&name.raw, namelen, dispName,
sizeof(dispName),
servName, sizeof(servName),
NI_NUMERICHOST | NI_NUMERICSERV) != 0)
throw QPID_POSIX_ERROR(rc);
return std::string(dispName) + ":" + std::string(servName);
} else {
if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName,
sizeof(dispName), 0, 0, NI_NUMERICHOST) != 0)
throw QPID_POSIX_ERROR(rc);
return dispName;
}
}
std::string getService(int fd, bool local)
{
// JPK: Essentially duplicating the code above.
::socklen_t namelen = sizeof(name);
PRNetAddr name;
PRStatus result;
::socklen_t namelen = sizeof(name);
if (local) {
result = PR_GetSockame(fd, &name);
} else {
result = PR_GetPeerName(fd, &name);
}
if (result == PR_FAILURE) {
// TODO: PR_GetErrorText, GetErrorTextLength, etc. here
}
char servName[NI_MAXSERV];
if (int rc=::getnameinfo((::sockaddr*)&name.raw, namelen, 0, 0,
servName, sizeof(servName),
NI_NUMERICHOST | NI_NUMERICSERV) != 0)
throw QPID_POSIX_ERROR(rc);
return servName;
}
}
SSLSocket::SSLSocket() :
IOHandle(new IOHandlePrivate)
{
SECStatus secStatus;
char * dir = ".";
// JPK: Here, we need to initialize the NSS routines (read in the
// certs, etc.) This is only the initial stuff - there are still
// per-connection init to do such as get and verify certificates
if (_sys_HAS_RUN_NSS_INIT == false)
{
_sys_HAS_RUN_NSS_INIT = true;
PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
secStatus = NSS_Init(dir);
if (secStatus != SECSuccess)
{
throw QPID_POSIX_ERROR(-1);
}
// Need to change this so that the policy is specified in
// the config file.
secStatus = NSS_SetDomesticPolicy();
if (secStatus != SECSuccess)
{
throw QPID_POSIX_ERROR(-1);
}
}
impl->prfdk = NULL;
}
SSLSocket::SSLSocket(IOHandlePrivate* h) :
IOHandle(h)
{}
void SSLSocket::createTcp() const
{
PRFileDesc & sslSocket = impl->prdfk;
PRSocketOptionData socketOption;
PRStatus prStatus;
PRFileDesc *tcpSocket;
if (sslSocket != NULL)
{
throw Exception(QPID_MSG("Error: running createTCP on a File Descriptor
that is already used."));
}
tcpSocket = PR_NewTCPSocket()
if (tcpSocket == NULL)
{
throw Exception(QPID_MSG("Error creating socket: " <<
h_errstr(h_errno)));
}
socketOption.option = PR_SockOpt_Nonblocking;
socketOption.value.non_blocking = PR_FALSE;
prStatus = PR_SetSocketOption(tcpSocket, &socketOption);
if (prStatus != PR_SUCCESS) {
errWarn("PR_SetSocketOption");
PR_Close(tcpSocket);
return NULL;
}
sslSocket = SSL_ImportFD(NULL, tcpSocket)
if (!sslSocket)
{
PR_Close(tcpSocket);
throw Exception(QPID_MSG("Error with SSLSocket: " <<
h_errstr(h_errno)));
}
tcpSocket = NULL;
return;
}
void SSLSocket::setTimeout(const Duration& interval) const
{
PRSocketOptionData prsod;
PRInternalTime pritDuration;
long DurationInSeconds = 0;
// JPK TODO: use NSPR functions to set the timeout on our NSS socket. DONE.
// Actually, it appears that there is no socket-level timeout, so we'll
merely
// set the timeout value in the class itself. This timeout value will be
// passed to all calls for network access.
/* WARNING:
The increasing interval value represented by PRIntervalTime wraps.
It should therefore never be used for intervals greater than
approximately 6 hours. Interval times are accurate regardless of
host processing requirements and are very cheap to acquire.
http://developer.mozilla.org/en/docs/PRIntervalTime
*/
// Change Duration into seconds:
DurationInSeconds = interval / 1000;
// Change seconds into PRIntervalTime:
pritDuration = DurationInSeconds;
impl->SocketTimeout = pritDuration;
}
void SSLSocket::setNonblocking() const {
PRSocketOptionData prsod;
PRStatus status;
prsod.option.non_blocking = PR_TRUE;
status = PR_SetSocketOption(impl->prfdk, &prsod);
// JPK: TODO: error handling the right way.
if (status == bad)
{
throw QPID_POSIX_ERROR(errno);
}
}
void SSLSocket::setBlocking() const {
PRSocketOptionData prsod;
PRStatus status;
prsod.option.non_blocking = PR_FALSE;
status = PR_SetSocketOption(prfdk, &prsod);
// JPK: TODO: error handling the right way.
if (status == bad)
{
throw QPID_POSIX_ERROR(errno);
}
}
namespace {
const char* h_errstr(int e) {
switch (e) {
case HOST_NOT_FOUND: return "Host not found";
case NO_ADDRESS: return "Name does not have an IP address";
case TRY_AGAIN: return "A temporary error occurred on an authoritative
name server.";
case NO_RECOVERY: return "Non-recoverable name server error";
default: return "Unknown error";
}
}
}
void SSLSocket::connect(const std::string& host, int port) const
{
// JPK: Todo: Use the PR_GetHostByName, and don't forget to import the
open socket into the
// NSPR SSL routines. Refer to pg. 6 of 8 in the SSLSample client doc.
//prfdk is the ssl socket as it will exist
PRNetAddr addr;
PRStatus prStatus;
PRHostEnt prHostEntry;
PRIntn hostenum;
PRFileDesc *tcpSocket;
SECStatus secStatus;
char buffer[PR_NETDB_BUF_SIZE];
char *hostName;
PRFileDesc &sslSocket = impl->prfdk;
if (sslSocket == NULL)
{
throw Exception(QPID_MSG("Trying to connect with NULL sslSocket!"));
}
hostName = new char [host.size()+1];
strcpy(hostName, host.c_str());
prStatus = PR_GetHostByName(hostName, buffer, 256, &hostEntry);
if (prStatus != PR_SUCCESS)
{
// JPK TODO: Handle error gracefully
throw Exception(QPID_MSG("Cannot resolve " << host << ": " <<
h_errstr(h_errno)));
}
prStatus = PR_EnumerateHostEnt(0, &hostEntry, port, &addr);
if (prStatus != PR_SUCCESS)
{
// JPK TODO: Handle error gracefully
throw Exception(QPID_MSG("Cannot resolve " << host << ": " <<
h_errstr(h_errno)));
}
// JPK TODO: IPv6 stuff. Note that this *may* be as simple as recompiling
NSPR
// with the IPv6 enabled, and modifying appropriately in this code - more
// research is needed.
// handshake as client
secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
if (secStatus != SECSuccess) {
throw Exception(QPID_MSG("Error setting SSL_HANDSHAKE_AS_CLIENT
option!"));
}
// enable full duplex
secStatus = SSL_OptionSet(sslSocket, SSL_ENABLE_FDX, PR_TRUE);
if (secStatus != SECSuccess) {
throw Exception(QPID_MSG("Error setting SSL_ENABLE_FDX option!"));
}
pr_status = PR_Connect(tcpSocket, addr, PR_INTERVAL_NO_TIMEOUT);
if (pr_status != PR_SUCCESS)
{
PR_Close(tcpSocket);
throw Exception(QPID_MSG("Error connecting: " << h_errstr(h_errno)));
}
secStatus = SSL_ResetHandshake(sslSocket, /* asServer */ PR_FALSE);
if (secStatus != SECSuccess) {
throw Exception(QPID_MSG("Error sending initial SSL_ResetHandshake!"));
}
// At this point we need to do maintenance tasks such as setting up the
certificates and
// password functions for private certs, etc.
return;
}
void
SSLSocket::close() const
{
PRFileDesc &sslSocket = impl->prfdk;
PRStatus prStatus;
prStatus = PR_Close(sslSocket);
if (prStatus != PR_SUCCESS)
{
throw Exception(QPID_MSG("Error closing SSL socket!"));
}
sslSocket = NULL;
}
ssize_t
SSLSocket::send(const void* data, size_t size) const
{
PRFileDesc &sslSocket = impl->prfdk;
PRStatus prStatus;
prStatus = PR_Write(sslSocket, data, size);
if (pr_Status != PR_Success)
{
throw Exception(QPID_MSG("Error writing data to SSL socket!"));
}
return sent;
}
ssize_t
SSLSocket::recv(void* data, size_t size) const
{
PRFileDesc &sslSocket = impl->prfdk;
PRStatus prStatus;
PRInt32 prRetv;
prRetv = PR_Read(sslSocket, data, size);
if (prRetv < 0) {
if (errno == ETIMEDOUT) return SOCKET_TIMEOUT;
throw QPID_POSIX_ERROR(errno);
}
return prRetv;
}
int SSLSocket::listen(int port, int backlog) const
{
PRFileDesc &sslSocket = impl->prfdk;
PRStatus prStatus;
PRSocketOptionData prOptReuseAddr;
PRNetAddr prListeningNetAddr;
PRNetAddr returnValue;
int yes=1;
// JPK Mental Note: what does the following do? Simply tells the socket
// that other things can listen on this port if they are not active,
// i.e. after a fast server bounce.
//QPID_POSIX_CHECK(setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)));
prOptReuseAddr.option = PR_SockOpt_Reuseaddr;
prOptReuseAddr.value = PR_TRUE;
prStatus = PR_SetSocketOption(sslSocket, &prOptReuseAddr);
if (prStatus != PR_Success)
{
throw Exception(QPID_MSG("Error setting PR_SockOpt_Reuseaddr option of
SSL socket!"));
}
// JPK TODO: Add an option to configure which IP address we're listening on.
prListeningNetAddr.inet.ip = PR_htonl(PR_INADDR_ANY);
prListeningNetAddr.inet.port = port;
prStatus = PR_Bind(sslSocket, prListeningNetAddr);
if (prStatus != PR_Success)
{
throw Exception(QPID_MSG("Error binding SSL socket during call to
listen!"));
}
prStatus = PR_Listen(sslSocket, backlog);
if (prStatus != PR_Success)
{
throw Exception(QPID_MSG("Error listening on SSL socket during call
to listen!"));
}
prStatus = PR_GetSockName(sslSocket, &returnValue);
if (prStatus != PR_Success)
{
throw Exception(QPID_MSG("Error on PR_GetSockName SSL socket during
call to listen!"));
}
return ntohs(returnValue.inet.port);
}
SSLSocket* SSLSocket::accept(struct sockaddr *addr, socklen_t *addrlen) const
{
// JPK TODO: NSPR function here.
int afd = ::accept(impl->fd, addr, addrlen);
if ( afd >= 0)
return new SSLSocket(new IOHandlePrivate(afd));
else if (errno == EAGAIN)
return 0;
else throw QPID_POSIX_ERROR(errno);
}
int SSLSocket::read(void *buf, size_t count) const
{
// JPK TODO: NSPR function here.
return ::read(impl->fd, buf, count);
}
int SSLSocket::write(const void *buf, size_t count) const
{
// JPK TODO: NSPR function here.
return ::write(impl->fd, buf, count);
}
std::string SSLSocket::getSockname() const
{
// JPK TODO: NSPR function here.
return getName(impl->fd, true);
}
std::string SSLSocket::getPeername() const
{
// JPK TODO: NSPR function here.
return getName(impl->fd, false);
}
std::string SSLSocket::getPeerAddress() const
{
// JPK TODO: NSPR function here.
return getName(impl->fd, false, true);
}
std::string SSLSocket::getLocalAddress() const
{
// JPK TODO: NSPR function here.
return getName(impl->fd, true, true);
}
uint16_t SSLSocket::getLocalPort() const
{
// JPK TODO: NSPR function here.
return atoi(getService(impl->fd, true).c_str());
}
uint16_t SSLSocket::getRemotePort() const
{
// JPK TODO: NSPR function here.
return atoi(getService(impl->fd, true).c_str());
}
}} // namespace qpid::sys