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

Reply via email to