/* -*- mode: C; c-basic-offset: 4  -*- */ 
#include "csink.h"
#include "csinkinet.h"
#include "csinksocket.h"
#include "csinkssl.h"
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>

#include "csink.h"
#include "csinkinet.h"
#include "csinkssl.h"

/* API functions. */
static void csink_ssl_free (CSinkSSL * sink);
static void csink_ssl_close (CSinkSSL * sink);
static gint csink_ssl_write (CSinkSSL * sink_, CBuf * message);
static void csink_ssl_can_write_action (CSink * sink_);
static void csink_ssl_can_read_action (CSink *sink_);
static void csink_ssl_accept_action (CSink * sink_);
static void csink_ssl_open_action (CSink * sink_);
static void csink_ssl_open (CSinkSSL * sink);
static gint csink_ssl_listen (CSinkSSL * sink);

void csink_ssl_init (CSinkSSL * sink);


/* Internal functions. */
static int csink_ssl_verify_callback (int ok, X509_STORE_CTX *ctx);

#define SSL_MAX_WRITE_CHUNKS 3


static int csink_ssl_seed_random (void)
{
    FILE *random_file;
    char filename[1024] = "";

    int i;
    GTimeVal cur_time;			/* The current timestamp. */
    unsigned char last_byte;		/* The last byte of the long. */
    unsigned char other_byte;		/* The last byte of the long. */

    CDEBUG (("csinkssl", "checking for /dev/urandom\n"));

    random_file = fopen ("/dev/urandom", "r");
    if (NULL != random_file) {
        /* They have a /dev/urandom, everything is gonna be alright. */
        fclose (random_file);
	return TRUE;
    }

    /* Use openssl builtins for getting the default random data. */
    CDEBUG (("csinkssl", "checking ~/.rnd and $RND_FILE\n"));
    RAND_file_name (filename, 1023);
    RAND_load_file (filename, 16384);	/* Pull up to 16k. */


    /* Start getting our own seed info if we don't have any of the defaults
     * for random data. */
    CDEBUG (("csinkssl", "gathering machine clock entropy\n"));
    for (i = 0; i < 200; i++) {
    	/* If RAND_status returns 1 then it has been seeded. */
        if (RAND_status () ) {
            CDEBUG (("csinkssl", "entropy successfully gathered.\n"));
            return TRUE;
        }

        g_get_current_time (&cur_time);
	last_byte = 0xff & cur_time.tv_usec;
        other_byte = 0xff & (cur_time.tv_usec / 256);

	/* Pass in the address of 'last_byte' so it can start reading there. */
    	CDEBUG (("csinkssl", "Adding a byte to our PRNG state"));
	RAND_seed (&last_byte, 1);		/* Read a whole byte off. */
	usleep ( (unsigned long) other_byte);	/* Sleep for a tiny bit. */
    }

    /* If we were unable to successfully seed the random number generator
     * we need to return FALSE. */
    CDEBUG (("csinkssl", "No randomness found, erroring out\n"));
    return FALSE;
}


void csink_ssl_init (CSinkSSL * sink)
{
    CDEBUG (("csinkssl", "initing an ssl sink."));

    /* Piggyback socket constructor for our own selfish plans. */
    csink_inet_init (CSINK_INET (sink));
    CSINK (sink)->csink_type = CSINK_SSL_TYPE;

    /* Set our method table. */
    CSINK (sink)->open = (CSinkOpenFunc) csink_ssl_open;
    CSINK (sink)->free = (CSinkFreeFunc) csink_ssl_free;
    CSINK (sink)->close = (CSinkCloseFunc) csink_ssl_close;
    CSINK (sink)->create = (CSinkCreateFunc) csink_ssl_create;

    CSINK (sink)->write = (CSinkWriteFunc) csink_ssl_write; 

    /* socket methods */
    CSINK_SOCKET (sink)->listen = (CSinkSocketListenFunc) csink_ssl_listen;

    /* socket callbacks */
    CSINK_SOCKET (sink)->open_action = 
	(CSinkSocketCanReadFunc) csink_ssl_open_action;
    CSINK_SOCKET (sink)->accept_action = 
	(CSinkCallbackFunc) csink_ssl_accept_action;
    CSINK_SOCKET (sink)->can_read_action = 
	(CSinkSocketCanReadFunc) csink_ssl_can_read_action;


    if (0 == csink_ssl_seed_random ())		/* Unable to seed. */
        csink_on_error (CSINK (sink), "SSL_NOSEED");

    sink->ssl = SSL_new (sink->ctx);

    sink->status = SSS_NOTCONNECTED;
    sink->must_write = FALSE;
    sink->must_read = FALSE;
}


static int ssl_is_initialized = FALSE;
static void
csink_ssl_init_check (void)
{
    if (FALSE == ssl_is_initialized) {
        /* Ensure that openssl has bee initialized. */
	CDEBUG (("csinkssl", "initing ssl libraries."));
	SSL_library_init ();
        SSL_load_error_strings ();
	ERR_load_crypto_strings ();

        /* SSL_library_init (); */      
    }
    
    ssl_is_initialized = TRUE;
}


CSinkSSL * csink_ssl_create (CSinkSSL * old_sink)
{
    static int local_context = 1;
    CSinkSSL * sink;

    CDEBUG (("csinkssl", "in csink_ssl_create"));

    /* make sure the ssl libraries are initialized before we go ahead and try
       using them */
    csink_ssl_init_check ();

    sink = g_new0 (CSinkSSL, 1);

    csink_ssl_init (sink);

    if (NULL == old_sink) {
        /* Set our SSL params. */
        sink->meth = SSLv3_method ();
        sink->ctx = SSL_CTX_new (sink->meth);
        SSL_CTX_set_options (sink->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);

        /* From s_server.c: "anything will do". */
        sink->session_id_context = local_context++;

        /* Not sure what the heck this guys does, but it looks 
         * like we might need it. */
        SSL_CTX_set_session_id_context (sink->ctx,
					(void*) &sink->session_id_context,
					sizeof (sink->session_id_context));

        /* 1 away seems to work ok. */
        sink->verify_depth = 1; /* Don't use anything smaller! */
    } else {
        /* Clone out of the old sink. */
        sink->meth = old_sink->meth;
        sink->ctx = old_sink->ctx;
        sink->verify_depth = old_sink->verify_depth;
    }

    if (NULL == old_sink)
        return sink;

    /* Need to clone stuff out of old_sink. */
    csink_set_new_data_func (CSINK (sink), CSINK (old_sink)->on_new_data);
    csink_set_close_func    (CSINK (sink), CSINK (old_sink)->on_close);
    csink_set_connect_func  (CSINK (sink), CSINK (old_sink)->on_connect);
    csink_set_error_func    (CSINK (sink), CSINK (old_sink)->on_error);
    csink_set_user_data     (CSINK (sink), CSINK (old_sink)->user_data);

    return sink;
}

void
csink_ssl_set_certcheck_func (CSinkSSL *sink, CSinkSSLOnCertCheckFunc func)
{
  sink->on_cert_check = func;
}


void csink_ssl_set_certfile (CSinkSSL *sink, char * cert_file)
{

    CDEBUG (("csinkssl", "in csink_ssl_set_certfile"));

    if (sink->cert_file != NULL)
        g_free (sink->cert_file);

    sink->cert_file = g_strdup (cert_file);

    csink_ssl_cert_info (sink);
}


void csink_ssl_set_certdir (CSinkSSL * sink, char * cert_dir)
{
    CDEBUG (("csinkssl", "in csink_ssl_set_certdir"));

    if (sink->cert_dir != NULL)
        g_free (sink->cert_dir);

    sink->cert_dir = g_strdup (cert_dir);

    csink_ssl_cert_info (sink);
}



int csink_ssl_cert_info (CSinkSSL * sink)
{
    int ret;
    char * cert_file;
    char * cert_dir;

    cert_dir = sink->cert_dir;
    cert_file = sink->cert_file;
   

    ret = SSL_CTX_load_verify_locations
		(sink->ctx, cert_file, cert_dir);  /* XXX */

    /* See if the user specified path is set right... */
    if (ret == 0) {

        CDEBUG (("csinkssl", "can't use user specified paths."));

        /* Try the default if the user path is wrong. */
        ret = SSL_CTX_set_default_verify_paths (sink->ctx); /* XXX */
        if (ret == 0) {
            /* Error. No verify path could be found. */
            CDEBUG (("csinkssl", "can't use default paths."));
            return FALSE;
        }
    }

    if (cert_file)
        CDEBUG (("csinkssl", "cert_file = %s", cert_file));

    ret = SSL_CTX_use_certificate_file (sink->ctx, /* XXX */
					cert_file, SSL_FILETYPE_PEM);
    if (ret <= 0) {
        /* Can't find cert in file. */
        CDEBUG (("csinkssl", "can't find cert file."));
        return FALSE;
    }
   
    ret = SSL_CTX_use_PrivateKey_file (sink->ctx, /* XXX */
                        cert_file, SSL_FILETYPE_PEM);
    if (ret <= 0) {
        /* Can't find public key in file. */
        CDEBUG (("csinkssl", "can't find public key in file."));
        return FALSE;
    }

    ret = SSL_CTX_check_private_key (sink->ctx); /* XXX */
    if (ret <= 0) {
        /* The private key doesn't match the public cert. */
        CDEBUG (("csinkssl", "private key doesn't match the public cert."));
        return FALSE;
    }


    SSL_CTX_set_verify_depth (sink->ctx, sink->verify_depth);

    /* Setup the verification scheme.  
     * Feature request: Let the user choose to do 'SSL_VERIFY_CLIENT_ONCE'. */
    SSL_CTX_set_verify (sink->ctx, 
		SSL_VERIFY_PEER 		| 
		SSL_VERIFY_CLIENT_ONCE, 
		csink_ssl_verify_callback);


    CDEBUG (("csinkssl", "cert paths are alright."));
    return TRUE;
}


/* We got the socket connection up, now we need to 
 * connect up the ssl protocol.  If we can't 
 * complete this time then we call ourselves again.
 */
static void csink_ssl_open_action (CSink * sink_)
{
    int res = 0;
    int err = 0;
    X509 * remote_cert;
    CSinkSSL * sink = CSINK_SSL (sink_);


    CDEBUG (("csinkssl", "in csink_ssl_open_action"));

    if (SSS_SSL_CONNECTED == sink->status) {
        return;
    }

    if (SSS_SSL_CONNECTING != sink->status) {
	if (!csink_socket_do_connect (CSINK_SOCKET (sink))) {
	    CDEBUG (("csinkssl", "open_action: inet connect failed, aborting."));
	    return;
	}
	sink->status = SSS_SSL_CONNECTING;
    }

    CDEBUG (("csinkssl", "open_action: inet connect good, doing ssl connect"));

    SSL_set_fd (sink->ssl, CSINK_SOCKET (sink)->fd);
    res = SSL_connect (sink->ssl);
    err = SSL_get_error (sink->ssl, res);


    CDEBUG (("csinkssl", "SSL_connect res = %i, err = %i", res, err));

    if (0 == res) {
	CDEBUG (("csinkssl", "open_action: handshake failure"));
	csink_on_error (CSINK (sink), "SSL_HANDSHAKE");
	csink_close (CSINK (sink));
	return;
    }

    if (1 == res) {
        /* Get cert here. */
        remote_cert = SSL_get_peer_certificate (sink->ssl);

        if (NULL == remote_cert) {
            csink_on_error (CSINK (sink), "SSL_NO_CERT");
            csink_close (CSINK (sink));
            /* Do we really /need/ a cert? */
            return;
        }

       res = SSL_get_verify_result (sink->ssl);
       if (X509_V_OK != res) {
            csink_on_error (CSINK (sink), "SSL_BAD_CERT");
            csink_close (CSINK (sink));
            return;
       } else {
            CDEBUG (("csinkssl", "good cert from server"));
       }

        /* Let the user check the cert. */
        if (sink->on_cert_check) {
	    err = sink->on_cert_check (sink, remote_cert);
            if (FALSE == err) {
                CDEBUG (("csinkssl", "User didn't like the cert. Closing..."));
                csink_close (CSINK (sink));
                return;
            }
        }
	CDEBUG (("csinkssl", "User accepted the cert."));	

        /* The ssl is connected up. */
        sink->status = SSS_SSL_CONNECTED;

        if (CSINK_SSL_TYPE == CSINK (sink)->csink_type) {
            CDEBUG (("csinkssl", "can write now..."));
            CSINK (sink)->flags = CSSF_CAN_WRITE;
        }
        
        /* We are ready to rock and roll. */
        csink_remove_fd (CSINK_SOCKET (sink)->read_watch_tag,
		"About to connect read tag, ensuring read tag is free.");

        CSINK_SOCKET (sink)->read_watch_tag =
		csink_add_fd (CSINK_SOCKET (sink)->fd, EIO_READ | EIO_ERROR,
			      csink_ssl_can_read_action, CSINK(sink),
			      "SSL Socket Read Tag");


        /* Call the user on connect function. */
        csink_on_connect (CSINK (sink));

        return;
    }

    if (-1 == res) {
	switch (err) {
        case SSL_ERROR_WANT_READ:

            /* Resetup the io watcher. */
            if (CSINK_SOCKET (sink)->read_watch_tag)
                csink_remove_fd (CSINK_SOCKET (sink)->read_watch_tag,
                "Adding read/write/err to ssl read tag");
            CSINK_SOCKET (sink)->read_watch_tag =
                csink_add_fd (CSINK_SOCKET (sink)->fd,
                              EIO_READ | EIO_ERROR,
                              csink_ssl_open_action, CSINK(sink),
                              "SSL Socket Read Tag (read/write/err added in)");

            break;
        case SSL_ERROR_WANT_WRITE:

            /* Resetup the io watcher. */
            if (CSINK_SOCKET (sink)->read_watch_tag)
                csink_remove_fd (CSINK_SOCKET (sink)->read_watch_tag,
                "Adding read/write/err to ssl read tag");
            CSINK_SOCKET (sink)->read_watch_tag =
                csink_add_fd (CSINK_SOCKET (sink)->fd,
                              EIO_WRITE | EIO_ERROR,
                              csink_ssl_open_action, CSINK(sink),
                              "SSL Socket Read Tag (read/write/err added in)");

            break;
        case SSL_ERROR_SYSCALL:

            if (EAGAIN != errno || EINTR != errno) {
                csink_on_error (CSINK(sink), "UNKNOWN");
                csink_close (CSINK (sink));
            } else {
               /* Resetup the io watcher. */
               if (CSINK_SOCKET (sink)->read_watch_tag)
                   csink_remove_fd (CSINK_SOCKET (sink)->read_watch_tag,
                   "Adding read/write/err to ssl read tag");
               CSINK_SOCKET (sink)->read_watch_tag =
                   csink_add_fd (CSINK_SOCKET (sink)->fd,
                              EIO_WRITE | EIO_READ | EIO_ERROR,
                              csink_ssl_open_action, CSINK(sink),
                              "SSL Socket Read Tag (read/write/err added in)");
            }
            break;

	default:
            CDEBUG (("csinkssl", "protocol error"));
            /* Big trouble, protocol error. */
            csink_on_error (CSINK(sink), "SSL_PROTOCOL");
            csink_close (CSINK (sink));

            break;
        }
    }
}

static void csink_ssl_open (CSinkSSL * sink)
{
    /* The idea is that csink sets up the inet conn just like regular, then
     * when our connect function is called, we hijack the fd and connect it
     * to the ssl stuff.
     * 
     * This of course all happens after csink_inet does a socket connect for
     * us, and in a callback, just to make things that much more fun.
     * 
     * Simple. 
     */

    CDEBUG (("csinkssl", "in csink_ssl_open"));

    if (CSINK_INET(sink)->socket.status & SOCKET_INET_DNS_INPROGRESS) {
	CDEBUG (("csinkssl", "open, dealing with DNS lookup..\n"));
	if (!(CSINK_INET(sink)->socket.status & SOCKET_CONNECT_INPROGRESS)) {
	    CSINK_INET(sink)->socket.status |= SOCKET_CONNECT_INPROGRESS;
	    csink_inet_set_on_dns_lookup_success
		(CSINK_INET(sink), (CSinkCallbackFunc)csink_ssl_open);
	    CDEBUG (("csinkinet",
		     "open, queued connect after lookup completes..\n"));
	}
	return;
    }


    sink->ssl = SSL_new (sink->ctx);
    SSL_clear (sink->ssl);

    sink->status = SSS_SSL_WAITING;
    CSINK (sink)->flags = CSSF_TRYING;    

    CDEBUG (("csinkssl", "open, calling socket open to finish up"));
    csink_socket_open (CSINK_SOCKET (sink));
    CDEBUG (("csinkssl", "open, returning"));
}

/* This guy is much like the ssl_connect function...
 * We will use the inet code to init the socket we are
 * using.  Then we can revert back to the user on connect
 * call back.
 */
static int
csink_ssl_listen (CSinkSSL * sink)
{
    CDEBUG (("csinkssl", "in csink_ssl_listen"));

    /* create the new SSL session */
    sink->ssl = SSL_new (sink->ctx);

    /* Setup the accepting. */
    sink->status = SSS_SSL_WAITING;
    return csink_inet_listen (CSINK_INET(sink));
}


static void
csink_ssl_accept_action (CSink * sink_)
{
    CSinkSSL * oldsink = CSINK_SSL(sink_);
    CSinkSSL * sink;
    int res;
    int err;
    X509 * remote_cert;


    CDEBUG (("csinkssl", "in csink_ssl_accept_action"));
    /* we've got the socket connected, now get the ssl protocol flowing */


    if (oldsink->status == SSS_SSL_WAITING) {
	/* only the listening socket gets it's state set to SSS_SSL_WAITING, so
	   we know that this is the first call to csink_ssl_accept_action. this
	   may get called more than once (see SSL_ERROR_WANT_READ below) so we
	   avoid creating this stuff twice */

	/* do the inet level accept */
	sink = CSINK_SSL (csink_inet_do_accept (CSINK_INET (oldsink)));
	if (NULL == sink) {
	    CDEBUG (("csinkssl", "accept_action, failed inet accept, returning."));
	    return;
	}
	
	CDEBUG (("csinkssl", "accept_action, done inet accept, on to ssl accept."));

	sink->ssl = SSL_new (sink->ctx);
	SSL_set_fd (sink->ssl, CSINK_SOCKET (sink)->fd);
	sink->status = SSS_SSL_ACCEPTING;
    } else {
	/* otherwise we know we've already made this and are passing it back to
	   ourselves. */
	CDEBUG (("csinkssl", "accept_action, back again, no inet setup needed."));
	sink = oldsink;
    }

    CDEBUG (("csinkssl", "accept_action, trying ssl accept."));
    res = SSL_accept (sink->ssl);
    err = SSL_get_error (sink->ssl, res);

    CDEBUG (("csinkssl", "SSL_accept = %i, err = %i", res, err));

    /* our SSL_accept worked fine */
    if (res == 1) {
	/* check the certificates */
        remote_cert = SSL_get_peer_certificate (sink->ssl);

        if (NULL == remote_cert) {
            csink_on_error (CSINK (sink), "SSL_NO_CERT");
            csink_close (CSINK (sink));
            return;
        }

	res = SSL_get_verify_result (sink->ssl);
	if (X509_V_OK != res) {
            csink_on_error (CSINK (sink), "SSL_BAD_CERT");
            csink_close (CSINK (sink));
            return;
	}

	/* Let the user check the cert. */
	if (sink->on_cert_check) {
            int accepted = sink->on_cert_check (sink, remote_cert);
            if (!accepted) {
                CDEBUG (("csinkssl",
                        "User didn't like the cert. Closing..."));
                csink_close (CSINK (sink));
                return;
            }
        }

        /* We are ready to rock and roll. */
        csink_remove_fd (CSINK_SOCKET (sink)->read_watch_tag, 
		"SSL Sink accept()ed, freeing read tag");

        CSINK_SOCKET (sink)->read_watch_tag =
                csink_add_fd (CSINK_SOCKET (sink)->fd, EIO_READ,
                        csink_ssl_can_read_action, CSINK(sink),
			"SSL Accept()ed Sink Tag");

        CDEBUG (("csinkssl", "calling connect"));

        /* The ssl is connected up. */
        sink->status = SSS_SSL_CONNECTED;
        if (CSINK_SSL_TYPE == CSINK (sink)->csink_type) {
            CSINK (sink)->flags = CSSF_CAN_WRITE;
        }

        /* Call the user on connect function. */
        csink_on_connect (CSINK (sink));

        return;
    }

    /* There was no fatal error but the handshake negotiation failed. */
    if (res == 0) {
	CDEBUG (("csinkssl", "accept_action, handshake failed."));
	csink_on_error (CSINK (sink), "SSL_HANDSHAKE");
	csink_close (CSINK (sink));
	return;
    }


    /* otherwise, we've got an error in connection. */
    CDEBUG (("csinkssl", "accept_action, got res=%i, err=%i", res, err));

    switch (err) {
    case SSL_ERROR_WANT_READ:
    case SSL_ERROR_WANT_WRITE:	
	CDEBUG (("csinkssl", "csink_ssl_accept: received MUST_(read/write)."));
	
	/* Resetup the io watcher to wait for what we need to complete
	   this SSL_accept */
	if (CSINK_SOCKET (sink)->read_watch_tag) {
	    csink_remove_fd (CSINK_SOCKET (sink)->read_watch_tag,
			     "Adding read/write/err for SSL accept");
	}	
	
	CSINK_SOCKET (sink)->read_watch_tag =
	    csink_add_fd (CSINK_SOCKET (sink)->fd,
			  EIO_READ | EIO_WRITE | EIO_ERROR,
			  csink_ssl_accept_action, 
			  CSINK(sink),
			  "SSL Accept tag (read/write/error added) ");
	return;
    default:
	/* Big trouble, protocol error. */
	CDEBUG (("csinkssl", "protocol error"));
	csink_on_error (CSINK(sink), "SSL_PROTOCOL");
	csink_close (CSINK (sink));
	return;
    }
}



static void
csink_ssl_close (CSinkSSL * sink)
{

    CDEBUG (("csinkssl", "in csink_ssl_close"));


    /* Stop the SSL session. */
    SSL_shutdown (sink->ssl); 
    SSL_free (sink->ssl);

    sink->status = SSS_NOTCONNECTED;

    csink_inet_close (CSINK_INET (sink));

    CSINK (sink)->flags = CSSF_CLOSED;
}



static void
csink_ssl_free (CSinkSSL * sink)
{
  csink_inet_release (CSINK_INET(sink));

  free (sink);
}

static gint
csink_ssl_write (CSinkSSL * sink, CBuf * message)
{
    CDEBUG (("csinkssl", "in csink_ssl_write"));

    if (FALSE == csink_write_sanity (CSINK(sink), message) ) {
        return FALSE;
    }

    CDEBUG (("csinkssl", "...sane..."));

    if (SSS_SSL_CONNECTED != sink->status) {
        /* Can't queue message. */
        csink_on_error (CSINK (sink), "DISCONN_WRITE");
        return FALSE;
    }

    CDEBUG (("csinkssl", "writing message %s", message->str));

    /* use csink_default_write to queue up the data. */
    csink_default_write (CSINK(sink), message);

    /* as long as this is set and we can write to the socket, we'll keep trying
       to write. */
    if (NULL == CSINK_SOCKET (sink)->write_watch_tag) {
        CSINK_SOCKET (sink)->write_watch_tag = 
		csink_add_fd (CSINK_SOCKET (sink)->fd,
			EIO_WRITE, 
			csink_ssl_can_write_action,
			CSINK (sink), 
			"SSL Write Tag");
    }

    return TRUE;
}


static void
csink_ssl_can_read_action (CSink *sink_)
{
  CSinkSSL *sink = CSINK_SSL(sink_);
  char buf[4096] = "";
  CBuf *newmsg;
  int failure;
  int res;
  int err;


  CDEBUG (("csinkssl", "in csink_ssl_can_read_action"));

  /* ensure that we are supposed to be here */
  if (sink->must_write) {
      /* openSSL says we /must/ write with the same parameters as before; so
	 we'll dispatch over to there first */
      CDEBUG (("csinkssl", "CALLING WRITE_ACTION FIRST"));
      sink->must_write = FALSE;
      csink_ssl_can_write_action (CSINK (sink));
      return;
  }
      

  /* Read a small block. */
  res = SSL_read (sink->ssl, buf, sizeof (buf) - 1);
  err = SSL_get_error (sink->ssl, res);

  /* How did we do? */
  if (res > 0) {
      CDEBUG (("csinkssl", "read off %i bytes.", res));
      CDEBUG (("csinkssl", "read in: %s", buf));
  }

  /* check errors */
  if (res <= 0) {
      char err_buf[4096];

      /* some information about what happened: */
      CDEBUG (("csinkssl", "got res <= 0:"));
      ERR_error_string (err, err_buf);
      CDEBUG (("csinkssl", "read_action: ssl error: %s", err_buf));

      failure = TRUE;		/* Assume it's bad. */

      switch (err) {
      case SSL_ERROR_ZERO_RETURN:
	  CDEBUG (("csinkssl", "read_action, sink was closed."));
	  csink_close (CSINK (sink));
	  break;

      case SSL_ERROR_WANT_WRITE:
	  CDEBUG (("csinkssl", 
		  "csink_ssl_can_read_action: SSL_ERROR_WANT_WRITE"));
	  sink->must_read = TRUE;

	  /*    if (NULL == CSINK_SOCKET (sink)->write_watch_tag) {
		CSINK_SOCKET (sink)->write_watch_tag = 
		csink_add_fd (CSINK_SOCKET (sink)->fd,
		EIO_WRITE, 
		csink_ssl_can_write_action,
		CSINK (sink), 
		"SSL Write tag (Added by ssl_can_read)");
		}
	  */
	  break;

      case SSL_ERROR_WANT_READ:	
	  CDEBUG (("csinkssl", 
		  "csink_ssl_can_read_action: SSL_ERROR_WANT_READ"));
	  sink->must_read = TRUE;

	  /*    if (NULL == CSINK_SOCKET (sink)->write_watch_tag) {
		CSINK_SOCKET (sink)->write_watch_tag = 
		csink_add_fd (CSINK_SOCKET (sink)->fd,
		EIO_WRITE, 
		csink_ssl_can_write_action,
		CSINK (sink), 
		"SSL Write tag (added by ssl_can_read");
		}
	  */

	  break;

      case SSL_ERROR_WANT_X509_LOOKUP:
	  /* need to check a new cert; this is unimplemented at
	   * present. */
	  csink_on_error (CSINK (sink), "SSL_WANT_CERT_CHECK");
	  csink_close (CSINK (sink));
	  break;

      case SSL_ERROR_SYSCALL:
	  CDEBUG (("csinkssl", "read_action, SSL_ERROR_SYSCALL"));
	  if (res == 0) {
	      csink_on_error (CSINK (sink), "SSL_PROTOCOL");
	      csink_close (CSINK (sink));
	      return;
	  }
	  switch (errno) {
	  case EPIPE:		/* Connection broken. */
	      csink_on_error (CSINK (sink), "SEND_PIPE");
	      csink_close (CSINK (sink));
	      break;
	  case EBADF:		/* Not a valid file descriptor. */
	      csink_on_error (CSINK (sink), "EBADF");
	      csink_close (CSINK (sink));
	      break;
	  case ENOTSOCK:	/* Not a valid socket. */
	      csink_on_error (CSINK (sink), "ENOTSOCK");
	      csink_close (CSINK (sink));
	      break;
	  case EMSGSIZE:
	      csink_on_error (CSINK (sink), "EMSGSIZE");
	      csink_close (CSINK (sink));
	      break;
	  case ENOTCONN:
	      csink_on_error (CSINK (sink), "INTERNAL");
	      csink_close (CSINK (sink));
	      break;
	  case EINTR:		/* interrupted system call; just try again */
	  case EAGAIN:
	      CDEBUG (("csinkssl", "read_action, --> EAGAIN, EINTR"));
	      break;
	  default:
	      CDEBUG (("csinkssl", "unknown errno value: %s", 
		      strerror (errno)));
	      csink_on_error (CSINK (sink), "UNKNOWN");
	      csink_close (CSINK (sink));
	  }
	  break;

      case SSL_ERROR_NONE:
	  CDEBUG (("csinkssl", "read_action, SSL_ERROR_NONE"));
	  failure = FALSE;
	  break;

      case SSL_ERROR_SSL:
	  CDEBUG (("csinkssl", "read_action, SSL_ERROR_SSL"));
	  csink_on_error (CSINK (sink), "SSL_PROTOCOL");
	  csink_close (CSINK (sink));
	  break;

      /* from here on, i'm taking these errors right out of the openssl sources, */
      /* not from the docs. */

      case SSL_ERROR_WANT_CONNECT:
	  CDEBUG (("csinkssl", "read_action, got SSL_ERROR_WANT_CONNECT"));
	  csink_on_error (CSINK (sink), "UNKNOWN");
	  csink_close (CSINK (sink));
	  break;

      default:
	  CDEBUG (("csinkssl", "(read_action) unknown openssl error."));
	  csink_on_error (CSINK (sink), "UNKNOWN");
	  csink_close (CSINK (sink));
	  ERR_print_errors_fp (stderr);
	  break;
      }
      
      if (failure) {
	  CDEBUG (("csinkssl", "failed, returning from function."));
	  return;
	  }
  }

  /* don't enqueue or signal empty messages, or buffers unfilled because of
   * errors. */
  if (res < 1) {
      CDEBUG (("csinkssl", "read_action: res was < 1, not adding buf to queue."));
      return;
  }

  /* Create a CBuf from the buf, add it to the queue. */
  newmsg = cbuf_new_with_data (buf, res);
  g_ptr_array_add (CSINK (sink)->inQueue, (gpointer) newmsg);

  /* Signal the user. */
  csink_on_new_data (CSINK(sink));
}




static void csink_ssl_can_write_action (CSink * sink_)
{
    CSinkSSL * sink = CSINK_SSL(sink_);
    int written = 0;
    int chunks_out = 0;
    CBuf *partial = NULL;
    CBuf *cur = NULL;
    int errcheck = FALSE;
    int res = 0;
    int err = 0;;

    CDEBUG (("csinkssl", "in csink_ssl_can_write_action"));

    /* ensure that we are supposed to be here: */
    if (SSS_SSL_CONNECTED != sink->status) {
        /* Need to clean up stuff, there is user error going on
         * if this is the case. We need to call the cleanup code.
         * We need to disable the select(2) on this fd. */
        /* FIXME. MW */
	CDEBUG (("csinkssl", "WHOOPS! not connected."));
        return;
    }
    if (sink->must_read) {
	/* openSSL says we /must/ read with the same parameters as before; so
	   we'll dispatch over to there first */
	CDEBUG (("csinkssl", "CALLING READ_ACTION FIRST"));
	sink->must_read = FALSE;
	csink_ssl_can_read_action (CSINK (sink));
	return;
    }

    CDEBUG (("csinkssl", "write_action: starting send loop."));

    /* Send loop for sending the queue. */
    chunks_out = 0;
    while (CSINK (sink)->outQueue->len > 0 
	   && chunks_out < SSL_MAX_WRITE_CHUNKS) {
	written = 0;

        cur = (CBuf *) g_ptr_array_index (CSINK (sink)->outQueue, 0);
        g_ptr_array_remove_index (CSINK (sink)->outQueue, 0);

        CDEBUG (("csinkssl", "Trying to write out '%s'\n", cur->str));

        while (written < cur->len) {
            CDEBUG (("csinkssl", "\tTrying to write out a piece.."));

            res = SSL_write (sink->ssl, cur->str + written, cur->len - written);
            err = SSL_get_error (sink->ssl, res);

            CDEBUG (("csinkssl", "res = %i, err = %i", res, err));

            if (res <= 0) {
		errcheck = TRUE;
		break; /* out of while */
	    } else {
                /* "The write operation was successful." */
                written += res;
            }
        }
	errcheck = FALSE;
	chunks_out++;
        cbuf_free (cur);
    }

    CDEBUG (("csinkssl", "Out of send loop.."));

    if (errcheck) {
	char err_buf[4096];
	ERR_error_string (err, err_buf);

	CDEBUG (("csinkssl", "Detected error condition:"));
	CDEBUG (("csinkssl", "write_action: ssl error: %s", err_buf));

	switch (err) {
	case SSL_ERROR_ZERO_RETURN:
	    CDEBUG (("csinkssl", "write_action: sink closed"));
	    csink_close (CSINK (sink));	    
	    break;

	case SSL_ERROR_WANT_WRITE:
	case SSL_ERROR_WANT_READ:
	    CDEBUG (("csinkssl", 
		     "csink_ssl_can_write_action: SSL_ERROR_WANT_READ"));
	    /* sink->must_write = TRUE; */
	    sink->must_read = TRUE;
	    break;

	case SSL_ERROR_WANT_X509_LOOKUP:
	    /* need to check a new cert; this is unimplemented at
	     * present. */
	    csink_on_error (CSINK (sink), "SSL_WANT_CERT_CHECK");
	    csink_close (CSINK (sink));
	    break;

	case SSL_ERROR_SYSCALL:
	    /* Some I/O error occurred. The OpenSSL error queue
	     * may contain more information on the error.  If
	     * the error queue is empty (i.e. ERR_get_error()
	     * returns 0), ret can be used to find out more
	     * about the error: If ret == 0, an EOF was
	     * observed that violates the protocol. If ret ==
	     * -1, the underlying BIO reported an I/O error
	     * (for socket I/O on Unix systems, consult errno
	     * for details).
	     */
	    CDEBUG (("csinkssl", "write_action: SSL_ERROR_SYSCALL"));
	    if (res == 0) {
		csink_on_error (CSINK (sink), "SSL_PROTOCOL");
		csink_close (CSINK (sink));
		break;
	    }
	    CDEBUG (("csinkssl", "write_action: syscall error .."));
	    switch (errno) {
	    case EPIPE:     /* Connection broken. */
		csink_on_error (CSINK (sink), "SEND_PIPE");
		csink_close (CSINK (sink));
		break;
	    case EBADF:     /* Not a valid socket file descriptor. */
		csink_on_error (CSINK (sink), "EBADF");
		csink_close (CSINK (sink));
		break;
	    case ENOTSOCK:
		csink_on_error (CSINK (sink), "ENOTSOCK");
		csink_close (CSINK (sink));
		break;
	    case EMSGSIZE:
		csink_on_error (CSINK (sink), "EMSGSIZE");
		csink_close (CSINK (sink));
		break;
	    case ENOTCONN:
		csink_on_error (CSINK (sink), "INTERNAL");
		csink_close (CSINK (sink));
		break;
	    case EINTR:     /* interrupted system call; just try again */
	    case EAGAIN:
		CDEBUG (("csinkssl", "write_action: EGAIN."));
		break;
	    default:
		csink_on_error (CSINK (sink), "UNKNOWN");
		csink_close (CSINK (sink));
		break;
	    }
	    break;

	case SSL_ERROR_NONE:
	    /* No error, we just actually wrote off 0 bytes. */
	    /* Don't worry, we'll be called again soon enough. */
	    break;

	case SSL_ERROR_SSL:
	    /* Generic protocol error. */	    
	    csink_on_error (CSINK (sink), "SSL_PROTOCOL");	    
	    csink_close (CSINK (sink));
	    break;

        /* from here on, i'm taking these errors right out of the openssl sources, */
        /* not from the docs. */

	case SSL_ERROR_WANT_CONNECT:
	    CDEBUG (("csinkssl", "read_action, got SSL_ERROR_WANT_CONNECT"));
	    csink_on_error (CSINK (sink), "UNKNOWN");
	    csink_close (CSINK (sink));
	    break;


	default:
	    CDEBUG (("csinkssl", "(write)unknown openssl error.\n"));
	    ERR_print_errors_fp (stderr);
	    csink_on_error (CSINK (sink), "UNKNOWN");
	    csink_close (CSINK (sink));
	    break;
	}

	/* save back what wasn't written and return. */
	partial = cbuf_new_with_data (cur->str+written, 
				      cur->len-written);
	g_ptr_array_add_at_start (CSINK(sink)->outQueue,
				  (gpointer)partial);
	cbuf_free (cur);


	return;
    }

    /* don't disconnect write tag if we are not done writing. */
    if (CSINK (sink)->outQueue->len != 0)
	return;

    CDEBUG (("csinkssl", "Done sending, disconnecting write tag."));
    
    /* Disconnect write tag now that everthing is done being
     * sent. */
    /* note that it might not have been set in the first place if we were
       dispatched here by must_write */
    if (CSINK_SOCKET (sink)->write_watch_tag) {
	csink_remove_fd (CSINK_SOCKET (sink)->write_watch_tag,
	  "SSL write Queue empty");
	CSINK_SOCKET (sink)->write_watch_tag = NULL;
    }
}


/* The ssl callback used to verify the new connection's
 * cert. */
static int csink_ssl_verify_callback (int ok,
				X509_STORE_CTX *ctx)
{
  CDEBUG (("csinkssl", "in csink_ssl_verify_callback"));

  if (0 == ok) {
      /* Verify error. */
      /* FIXME: The certificate might be farther away from the Authority that
       * allowed by default, but this sink should be able to decide. */
  }

  
  switch (ctx->error) {
  case X509_V_ERR_CERT_HAS_EXPIRED:
  case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
      /* FIXME: its possible that we want to accept certs even if they expired.
       * Verisign is a racket. */
      break;

  default:
      /* Everything else. */
      break;
  }
  
  return (ok);		/* Pass the error on. */
}




syntax highlighted by Code2HTML, v. 0.9.1