/* -*- mode: C; c-basic-offset: 4 -*- */ #include "csink.h" #include "csinkinet.h" #include "csinksocket.h" #include "csinkssl.h" #include #include #include #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. */ }