/* -*- 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