/*=============================================================================
                                 socket_openssl.c
===============================================================================
  This is the implementation of TChanSwitch and TChannel
  for an SSL (Secure Sockets Layer) connection based on an OpenSSL
  connection object -- what you create with SSL_new().

  This is just a template for future development.  It does not function
  (or even compile) today.

=============================================================================*/

#include "xmlrpc_config.h"

#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>

#include "xmlrpc-c/util_int.h"
#include "xmlrpc-c/string_int.h"
#include "bool.h"
#include "mallocvar.h"
#include "trace.h"
#include "chanswitch.h"
#include "channel.h"
#include "socket.h"
#include "xmlrpc-c/abyss.h"

#include "socket_openssl.h"



struct channelOpenSsl {
/*----------------------------------------------------------------------------
   The properties/state of a TChannel unique to the OpenSSL variety.
-----------------------------------------------------------------------------*/
    SSL * sslP;
        /* SSL connection handle (such as is created by SSL_new() in
           the openssl library).
        */
    bool userSuppliedConn;
        /* The SSL connection belongs to the user; we did not create
           it.
        */
};



static bool
connected(int const fd) {
/*----------------------------------------------------------------------------
   Return TRUE iff the socket on file descriptor 'fd' is in the connected
   state.

   If 'fd' does not identify a stream socket or we are unable to determine
   the state of the stream socket, the answer is "false".
-----------------------------------------------------------------------------*/
    bool connected;
    struct sockaddr sockaddr;
    socklen_t nameLen;
    int rc;

    nameLen = sizeof(sockaddr);

    rc = getpeername(fd, &sockaddr, &nameLen);

    if (rc == 0)
        connected = TRUE;
    else
        connected = FALSE;

    return connected;
}



void
SocketOpensslInit(const char ** const errorP) {

	SSL_load_error_strings();
        /* readable error messages, don't call this if memory is tight */
	SSL_library_init();   /* initialize library */

	/* actions_to_seed_PRNG(); */

	*errorP = NULL;
}



void
SocketOpenSslTerm(void) {

	ERR_free_string();

}



/*=============================================================================
      TChannel
=============================================================================*/

static ChannelDestroyImpl channelDestroy;

static void
channelDestroy(TChannel * const channelP) {

    struct channelOpenssl * const channelOpensslP = channelP->implP;

    if (!socketUnixP->userSuppliedConn)
        SSL_shutdown(channelOpensslP->sslP);

    free(channelOpensslP);
}



static ChannelWriteImpl channelWrite;

static void
channelWrite(TChannel *            const channelP,
             const unsigned char * const buffer,
             uint32_t              const len,
             bool *                const failedP) {

    struct channelOpenssl * const channelOpensslP = channelP->implP;

    int bytesLeft;
    bool error;

    assert(sizeof(int) >= sizeof(len));

    for (bytesLeft = len, error = FALSE;
         bytesLeft > 0 && !error;
        ) {
        int const maxSend = (int)(-1) >> 1;

        int rc;
        
        rc = SSL_write(channelOpensslP->ssl, &buffer[len-bytesLeft],
                       MIN(maxSend, bytesLeft));

        if (ChannelTraceIsActive) {
            if (rc <= 0)
                fprintf(stderr,
                        "Abyss socket: SSL_write() failed.  rc=%d (%s)",
                        rc, SSL_get_error(rc));
            else
                fprintf(stderr, "Abyss socket: sent %u bytes: '%.*s'\n",
                        rc, rc, &buffer[len-bytesLeft]);
        }
        if (rc <= 0)
            /* 0 means connection closed; < 0 means severe error */
            error = TRUE;
        else
            bytesLeft -= rc;
    }
    *failedP = error;
}



ChannelReadImpl channelRead;

static void
channelRead(TChannel *      const channelP, 
            unsigned char * const buffer, 
            uint32_t        const bufferSize,
            uint32_t *      const bytesReceivedP,
            bool *          const failedP) {

    struct channelOpenssl * const channelOpensslP = channelP->implP;

    int rc;
    rc = SSL_read(channelOpensslP->sslP, buf, len);

    if (rc < 0) {
        *failedP = TRUE;
        if (ChannelTraceIsActive)
            fprintf(stderr, "Failed to receive data from OpenSSL connection.  "
                    "SSL_read() failed with rc %d (%s)\n",
                    rc, SSL_get_error(rc));
    } else {
        *failedP = FALSE;
        *bytesReceivedP = rc;

        if (ChannelTraceIsActive)
            fprintf(stderr, "Abyss channel: read %u bytes: '%.*s'\n",
                    *bytesReceivedP, (int)(*bytesReceivedP), buffer);
    }
}



static ChannelWaitImpl channelWait;

static void
channelWait(TChannel * const channelP,
            bool       const waitForRead,
            bool       const waitForWrite,
            uint32_t   const timeoutMs,
            bool *     const readyToReadP,
            bool *     const readyToWriteP,
            bool *     const failedP) {
/*----------------------------------------------------------------------------
  See socket_unix.c for an explanation of the purpose of this
  subroutine.

  We don't actually fulfill that purpose, though, because we don't know
  how yet.  Instead, we return immediately and hope that if Caller
  subsequently does a read or write, it blocks until it can do its thing.
-----------------------------------------------------------------------------*/

}



static ChannelInterruptImpl channelInterrupt;

static void
channelInterrupt(TChannel * const channelP) {
/*----------------------------------------------------------------------------
  Interrupt any waiting that a thread might be doing in channelWait()
  now or in the future.
-----------------------------------------------------------------------------*/

    /* This is trivial, since channelWait() doesn't actually wait */
}



static struct TChannelVtbl const channelVtbl = {
    &channelDestroy,
    &channelWrite,
    &channelRead,
    &channelWait,
    &channelInterrupt,
    &channelFormatPeerInfo,
};



static void
makeChannelInfo(struct abyss_openssl_chaninfo ** const channelInfoPP,
                SSL *                            const sslP,
                const char **                    const errorP) {

    struct abyss_openssl_chaninfo * channelInfoP;

    MALLOCVAR(channelInfoP);
    
    if (channelInfoP == NULL)
        xmlrpc_asprintf(errorP, "Unable to allocate memory");
    else {
        
        *channelInfoPP = channelInfoP;

        *errorP = NULL;
    }
}



static void
makeChannelFromSsl(SSL *         const sslP,
                   TChannel **   const channelPP,
                   const char ** const errorP) {

    struct channelOpenssl * channelOpensslP;

    MALLOCVAR(channelOpensslP);
    
    if (channelOpensslP == NULL)
        xmlrpc_asprintf(errorP, "Unable to allocate memory for OpenSSL "
                        "socket descriptor");
    else {
        TChannel * channelP;
        
        channelOpensslP->sslP = sslP;
        channelOpensslP->userSuppliedSsl = TRUE;
        
        /* This should be ok as far as I can tell */
        ChannelCreate(&channelVtbl, channelOpensslP, &channelP);
        
        if (channelP == NULL)
            xmlrpc_asprintf(errorP, "Unable to allocate memory for "
                            "channel descriptor.");
        else {
            *channelPP = channelP;
            *errorP = NULL;
        }
        if (*errorP)
            free(channelOpensslP);
    }
}



void
ChannelOpensslCreateSsl(SSL *                            const sslP,
                        TChannel **                      const channelPP,
                        struct abyss_openssl_chaninfo ** const channelInfoPP,
                        const char **                    const errorP) {

    assert(sslP);

    makeChannelInfo(channelInfoPP, sslP, errorP);
    if (!*errorP) {
        makeChannelFromSsl(ssl, channelPP, errorP);
        
        if (*errorP) {
            free(*channelInfoPP);
        }
    }
}



/*=============================================================================
      TChanSwitch
=============================================================================*/

struct opensslSwitch {
/*----------------------------------------------------------------------------
   The properties/state of a TChanSwitch uniqe to the OpenSSL variety.

   Note that OpenSSL deals only in connected sockets, so this switch
   doesn't really have anything to do with OpenSSL except that it
   creates OpenSSL TChannels.  The switch is just a POSIX listening
   socket, and is almost identical to the Abyss Unix channel switch.
-----------------------------------------------------------------------------*/
    int fd;
        /* File descriptor of the POSIX socket (such as is created by
           socket() in the C library) for the socket.
        */
    bool userSuppliedFd;
        /* The file descriptor and associated POSIX socket belong to the
           user; we did not create it.
        */
};



static SwitchDestroyImpl chanSwitchDestroy;

static void
chanSwitchDestroy(TChanSwitch * const chanSwitchP) {

static void
chanSwitchDestroy(TChanSwitch * const chanSwitchP) {

    struct opensslSwitch * const opensslSwitchP = chanSwitchP->implP;

    if (!opensslSwitchP->userSuppliedFd)
        close(opensslSwitchP->fd);

    free(opensslSwitchP);
}



static SwitchListenImpl chanSwitchListen;

static void
chanSwitchListen(TChanSwitch * const chanSwitchP,
                 uint32_t      const backlog,
                 const char ** const errorP) {

    struct opensslSwitch * const opensslSwitchP = chanSwitchP->implP;

    int32_t const minus1 = -1;

    int rc;

    /* Disable the Nagle algorithm to make persistant connections faster */

    setsockopt(opensslSwitchP->fd, IPPROTO_TCP, TCP_NODELAY,
               &minus1, sizeof(minus1));

    rc = listen(opensslSwitchP->fd, backlog);

    if (rc == -1)
        xmlrpc_asprintf(errorP, "listen() failed with errno %d (%s)",
                        errno, strerror(errno));
    else
        *errorP = NULL;
}



static SwitchAcceptImpl  chanSwitchAccept;

static void
chanSwitchAccept(TChanSwitch * const chanSwitchP,
                 TChannel **   const channelPP,
                 void **       const channelInfoPP,
                 const char ** const errorP) {
/*----------------------------------------------------------------------------
   Accept a connection via the channel switch *chanSwitchP.  Return as
   *channelPP the channel for the accepted connection.

   If no connection is waiting at *chanSwitchP, wait until one is.

   If we receive a signal while waiting, return immediately with
   *channelPP == NULL.
-----------------------------------------------------------------------------*/
    struct opensslSwitch * const listenSocketP = chanSwitchP->implP;

    bool interrupted;
    TChannel * channelP;

    interrupted = FALSE; /* Haven't been interrupted yet */
    channelP    = NULL;  /* No connection yet */
    *errorP     = NULL;  /* No error yet */

    while (!channelP && !*errorP && !interrupted) {
        struct sockaddr peerAddr;
        socklen_t size = sizeof(peerAddr);
        int rc;

        rc = accept(listenSocketP->fd, &peerAddr, &size);

        if (rc >= 0) {
            int const acceptedFd = rc;
            struct channelOpenssl * opensslChannelP;

            MALLOCVAR(opensslChannelP);

            if (!opensslChannelP)
                xmlrpc_asprintf(errorP, "Unable to allocate memory");
            else {
                struct abyss_openssl_chaninfo * channelInfoP;
                
                openChannelP->userSuppliedFd = FALSE;
                TODO("turn connected socket 'acceptedFd' into an OpenSSL "
                     "connection opensslChannelP->sslP");

                makeChannelInfo(&channelInfoP, peerAddr, size, errorP);
                if (!*errorP) {
                    *channelInfoPP = channelInfoP;

                    ChannelCreate(&channelVtbl, opensslChannelP, &channelP);
                    if (!channelP)
                        xmlrpc_asprintf(errorP,
                                        "Failed to create TChannel object.");
                    else
                        *errorP = NULL;

                    if (*errorP)
                        free(channelInfoP);
                }
                if (*errorP)
                    free(opensslChannelP);
            }
            if (*errorP)
                close(acceptedFd);
        } else if (errno == EINTR)
            interrupted = TRUE;
        else
            xmlrpc_asprintf(errorP, "accept() failed, errno = %d (%s)",
                            errno, strerror(errno));
    }
    *channelPP = channelP;
}



static SwitchInterruptImpl chanSwitchInterrupt;

static void
chanSwitchInterrupt(TChanSwitch * const chanSwitchP) {
/*----------------------------------------------------------------------------
  Interrupt any waiting that a thread might be doing in chanSwitchAccept()
  now or in the future.

  Actually, this is a no-op, since we don't yet know how to accomplish
  that.
-----------------------------------------------------------------------------*/

}



static struct TChanSwitchVtbl const chanSwitchVtbl = {
    &chanSwitchDestroy,
    &chanSwitchListen,
    &chanSwitchAccept,
};



static void
setSocketOptions(int           const fd,
                 const char ** const errorP) {

    int32_t n = 1;
    int rc;

    rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&n, sizeof(n));

    if (rc < 0)
        xmlrpc_asprintf(errorP, "Failed to set socket options.  "
                        "setsockopt() failed with errno %d (%s)",
                        errno, strerror(errno));
    else
        *errorP = NULL;
}



static void
bindSocketToPort(int              const fd,
                 struct in_addr * const addrP,
                 uint16_t         const portNumber,
                 const char **    const errorP) {

    struct sockaddr_in name;
    int rc;

    name.sin_family = AF_INET;
    name.sin_port   = htons(portNumber);
    if (addrP)
        name.sin_addr = *addrP;
    else
        name.sin_addr.s_addr = INADDR_ANY;

    rc = bind(fd, (struct sockaddr *)&name, sizeof(name));

    if (rc == -1)
        xmlrpc_asprintf(errorP, "Unable to bind socket to port number %hu.  "
                        "bind() failed with errno %d (%s)",
                        portNumber, errno, strerror(errno));
    else
        *errorP = NULL;
}



void
ChanSwitchOpensslCreate(unsigned short const portNumber,
                        TChanSwitch ** const chanSwitchPP,
                        const char **  const errorP) {
/*----------------------------------------------------------------------------
   Create an OpenSSL-based channel switch.

   Use an IP socket.

   Set the socket's local address so that a subsequent "listen" will listen
   on all IP addresses, port number 'portNumber'.
-----------------------------------------------------------------------------*/
    struct opensslSwitch * opensslSwitchP;

    MALLOCVAR(opensslSwitchP);

    if (!opensslSwitchP)
        xmlrpc_asprintf(errorP, "Unable to allocate memory for Openssl "
                        "channel switch descriptor structure.");
    else {
        int rc;
        rc = socket(AF_INET, SOCK_STREAM, 0);
        if (rc < 0)
            xmlrpc_asprintf(errorP, "socket() failed with errno %d (%s)",
                            errno, strerror(errno));
        else {
            opensslSwitchP->fd = rc;
            opensslSwitchP->userSuppliedFd = FALSE;

            setSocketOptions(opensslSwitchP->fd, errorP);
            if (!*errorP) {
                bindSocketToPort(opensslSwitchP->fd, NULL, portNumber, errorP);
                
                if (!*errorP)
                    ChanSwitchCreate(&chanSwitchVtbl, opensslSwitchP,
                                     chanSwitchPP);
            }
            if (*errorP)
                close(opensslSwitchP->fd);
        }
        if (*errorP)
            free(opensslSwitchP);
    }
}



void
ChanSwitchOpensslCreateFd(int            const fd,
                          TChanSwitch ** const chanSwitchPP,
                          const char **  const errorP) {
/*----------------------------------------------------------------------------
   Create an OpenSSL-based channel switch, based on a POSIX socket that
   is in listen state.
-----------------------------------------------------------------------------*/
    struct opensslSwitch * opensslSwitchP;

    if (connected(fd))
        xmlrpc_asprintf(errorP,
                        "Socket (file descriptor %d) is in connected "
                        "state.", fd);
    else {
        MALLOCVAR(opensslSwitchP);

        if (opensslSwitchP == NULL)
            xmlrpc_asprintf(errorP, "unable to allocate memory for Openssl "
                            "channel switch descriptor.");
        else {
            TChanSwitch * chanSwitchP;

            opensslSwitchP->fd = fd;
            opensslSwitchP->userSuppliedFd = TRUE;
            
            ChanSwitchCreate(&chanSwitchVtbl, opensslSwitchP, &chanSwitchP);

            if (chanSwitchP == NULL)
                xmlrpc_asprintf(errorP, "Unable to allocate memory for "
                                "channel switch descriptor");
            else {
                *chanSwitchPP = chanSwitchP;
                *errorP = NULL;
            }
            if (*errorP)
                free(opensslSwitchP);
        }
    }
}
