/*
   SSL Stream (used by Client and Server).

   To understand how to program SSL,
   I strongly recommend the O'Reilly book:

   Network Security with OpenSSL.
   John Viega, Matt Messier and Pravir Chandra.

   The book will be referenced as [OREILLY].
   Page numbers refer to the 1st Edition, published 2002. */



#include "gskstreamssl.h"
#include "gskopensslbiostream.h"
#include <openssl/rand.h>
#include <openssl/err.h>
#include "../gskbufferstream.h"
#include "../gskstreamconnection.h"
#include "../gskdebug.h"
#include "../debug.h"
#include "../gskmacros.h"
#include <string.h>

static GObjectClass *parent_class = NULL;

enum
{
  PROP_0,
  PROP_KEY_FILE, //???

  PROP_PASSWORD,

  /* only for clients. */
  PROP_CA_FILE,
  PROP_CA_DIR,

  /* for clients and servers. */
  PROP_CERT_FILE,
  PROP_IS_CLIENT
};

#define DEBUG(ssl, args)				\
  G_STMT_START{						\
    if (GSK_IS_DEBUGGING(SSL))				\
      {							\
	g_message args;					\
      }							\
  }G_STMT_END

/* --- stream and io methods --- */
static void
handle_transport_error (GskStream *transport,
                        GskStreamSsl *ssl)
{
  gsk_io_set_error (GSK_IO (ssl),
                    GSK_IO (transport)->error_cause,
                    GSK_IO (transport)->error->code,
                    "SSL: transport layer had error: %s",
                    GSK_IO (transport)->error->message);
}

static void
set_backend_flags_raw (GskStreamSsl *ssl,
		       gboolean want_read,
		       gboolean want_write)
{
  if (want_read && !ssl->backend_poll_read)
    {
      ssl->backend_poll_read = TRUE;
      if (ssl->backend)
	gsk_hook_unblock (gsk_buffer_stream_read_hook (ssl->backend));
    }
  else if (!want_read && ssl->backend_poll_read)
    {
      ssl->backend_poll_read = FALSE;
      if (ssl->backend)
	gsk_hook_block (gsk_buffer_stream_read_hook (ssl->backend));
    }

  if (want_write && !ssl->backend_poll_write)
    {
      ssl->backend_poll_write = TRUE;
      if (ssl->backend)
	gsk_hook_unblock (gsk_buffer_stream_write_hook (ssl->backend));
    }
  else if (!want_write && ssl->backend_poll_write)
    {
      ssl->backend_poll_write = FALSE;
      if (ssl->backend)
	gsk_hook_block (gsk_buffer_stream_write_hook (ssl->backend));
    }
}

static void
set_backend_flags_raw_to_underlying (GskStreamSsl *ssl)
{
  set_backend_flags_raw (ssl, ssl->this_readable, ssl->this_writable);
}

static gboolean
do_handshake (GskStreamSsl *stream_ssl, SSL* ssl, GError **error)
{
  int rv;
  DEBUG (stream_ssl, ("do_handshake[client=%u]: start", stream_ssl->is_client));
  rv = SSL_do_handshake (ssl);
  if (rv <= 0)
    {
      int error_code = SSL_get_error (ssl, rv);
      gulong l = ERR_peek_error();
      switch (error_code)
	{
	case SSL_ERROR_NONE:
	  stream_ssl->doing_handshake = 0;
	  set_backend_flags_raw_to_underlying (stream_ssl);
	  //g_message ("DONE HANDSHAKE (is-client=%u)", stream_ssl->is_client);
	  break;
	case SSL_ERROR_SYSCALL:
	case SSL_ERROR_WANT_READ:
	  set_backend_flags_raw (stream_ssl, FALSE, TRUE);
	  //g_message("do-handshake:want-read");
	  break;
	case SSL_ERROR_WANT_WRITE:
	  //g_message("do-handshake:want-write");
	  set_backend_flags_raw (stream_ssl, TRUE, FALSE);
	  break;
	default:
	  {
	    g_set_error (error,
			 GSK_G_ERROR_DOMAIN,
			 GSK_ERROR_BAD_FORMAT,
			 _("error doing-handshake on SSL socket: %s: %s [code=%08lx (%lu)] [rv=%d]"),
			 ERR_func_error_string(l),
			 ERR_reason_error_string(l),
			 l, l, error_code);
	    return FALSE;
	  }
	}
    }
  else
    {
      stream_ssl->doing_handshake = 0;
      set_backend_flags_raw_to_underlying (stream_ssl);
    }
  return TRUE;
}

static inline void
maybe_update_backend_poll_state (GskStreamSsl *ssl)
{
  gboolean backend_needs_write = ssl->this_readable;
  gboolean backend_needs_read = ssl->this_writable;

  if (ssl->read_needed_to_write)
    backend_needs_read = FALSE;
  else if (ssl->write_needed_to_read)
    backend_needs_write = FALSE;

  DEBUG (ssl, ("maybe_update_backend_poll_state: backend-needs: write=%d, read=%d",
	  backend_needs_write, backend_needs_read));


  set_backend_flags_raw (ssl, backend_needs_read, backend_needs_write);
}

static void
gsk_stream_ssl_set_poll_read  (GskIO      *io,
			       gboolean    do_poll)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (io);
  ssl->this_readable = do_poll ? 1 : 0;
  DEBUG (ssl, ("gsk_stream_ssl_set_poll_read: is_client=%d, do_poll=%d",
	   ssl->is_client, do_poll));
  maybe_update_backend_poll_state (ssl);
}

static void
gsk_stream_ssl_set_poll_write (GskIO      *io,
			       gboolean    do_poll)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (io);
  ssl->this_writable = do_poll ? 1 : 0;
  DEBUG (ssl, ("gsk_stream_ssl_set_poll_write: is_client=%d, do_poll=%d",
	  ssl->is_client, do_poll));
  maybe_update_backend_poll_state (ssl);
}

static gboolean
gsk_stream_ssl_shutdown_both (GskStreamSsl *ssl,
			      GError  **error)
{
  int rv;
  if (ssl->ssl == NULL)
    {
      gsk_io_notify_shutdown (GSK_IO (ssl));
      if (ssl->backend)
        gsk_io_shutdown (GSK_IO (ssl->backend), NULL);
      return TRUE;
    }
  rv = SSL_shutdown (ssl->ssl);
  if (rv == 0)
    {
      //g_message ("enterring shutting-down state [is-client=%u]", ssl->is_client);
      ssl->state = GSK_STREAM_SSL_STATE_SHUTTING_DOWN;
      gsk_io_write_shutdown (ssl->backend, NULL);
      gsk_buffer_stream_read_shutdown (GSK_BUFFER_STREAM (ssl->backend));
      gsk_io_notify_shutdown (GSK_IO (ssl));
      return TRUE;
    }
  else if (rv == 1)
    {
      DEBUG (ssl, ("shutdown! [is_client=%u]",ssl->is_client));
      ssl->state = GSK_STREAM_SSL_STATE_SHUT_DOWN;

      gsk_io_write_shutdown (ssl->backend, NULL);
      gsk_buffer_stream_read_shutdown (GSK_BUFFER_STREAM (ssl->backend));
      gsk_io_notify_shutdown (GSK_IO (ssl));

      return TRUE;
    }
  else
    {
      /* error handling */
      int error_code = SSL_get_error (ssl->ssl, rv);
      switch (error_code)
	{
	case SSL_ERROR_WANT_READ:
	  set_backend_flags_raw (ssl, FALSE, TRUE);
	  break;
	case SSL_ERROR_WANT_WRITE:
	  set_backend_flags_raw (ssl, TRUE, FALSE);
	  break;
	case SSL_ERROR_NONE:
	  break;
	default:
	  g_set_error (error,
		       GSK_G_ERROR_DOMAIN,
		       GSK_ERROR_IO,
		       "ssl error shutting down: code %d",
		       error_code);
	  //g_message("error with SSL_shutdown down: %s", (*error)->message);
	  return FALSE;
	}
      ssl->state = GSK_STREAM_SSL_STATE_SHUTTING_DOWN;
      return FALSE;
    }
}

static gboolean
gsk_stream_ssl_shutdown_read  (GskIO      *io,
			       GError    **error)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (io);
  if (!gsk_io_get_is_writable (io)
    || gsk_io_get_is_write_shutting_down (io))
    {
      //g_message("shutdown-both from read");
      return gsk_stream_ssl_shutdown_both (ssl, error);
    }

  /* NOTE: ssl does not support half-shutdowns... */
  return FALSE;
}

static gboolean
gsk_stream_ssl_shutdown_write (GskIO      *io,
			       GError    **error)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (io);
  if (!gsk_io_get_is_readable (io)
    || gsk_io_get_is_read_shutting_down (io)
    || ssl->got_remote_shutdown)
    {
      //g_message("shutdown-both from write");
      gsk_stream_ssl_shutdown_both (ssl, error);
      return TRUE;
    }

  /* NOTE: ssl does not support half-shutdowns... */
  return FALSE;
}

static guint
gsk_stream_ssl_raw_read       (GskStream     *stream,
			       gpointer       data,
			       guint          length,
			       GError       **error)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (stream);
  guint read_length;
  int ssl_read_rv;
  if (length == 0)
    return 0;
  if (ssl->doing_handshake)
    return 0;

process_has_read_buffer:
  if (ssl->read_buffer_length > 0)
    {
      guint rv = MIN (ssl->read_buffer_length, length);
      g_assert (rv > 0);
      memcpy (data, ssl->read_buffer, rv);
      ssl->read_buffer_length -= rv;
      if (ssl->read_buffer_length > 0)
	g_memmove (ssl->read_buffer, ssl->read_buffer + rv, ssl->read_buffer_length);
      return rv;
    }

  /* Make sure our buffer is long enough to accomodate the
     amount of data requested.  */
  if (!ssl->reread_length && ssl->read_buffer_alloc < length)
    {
      if (ssl->read_buffer_alloc == 0)
	ssl->read_buffer_alloc = 4096;
      while (ssl->read_buffer_alloc < length)
	ssl->read_buffer_alloc *= 2;
      ssl->read_buffer = g_realloc (ssl->read_buffer, ssl->read_buffer_alloc);
    }

  /* Do underlying read. */
  read_length = ssl->reread_length ? ssl->reread_length : length;
  ssl_read_rv = SSL_read (ssl->ssl, ssl->read_buffer, read_length);
  if (ssl_read_rv > 0)
    {
      ssl->reread_length = 0;
      ssl->read_buffer_length = ssl_read_rv;
      goto process_has_read_buffer;
    }
  if (ssl_read_rv == 0)
    {
      if (!gsk_io_get_is_readable (ssl->backend)
       || !gsk_io_get_is_writable (ssl->backend))
        {
          /* Either a clean shutdown occurred, or an error occurred in the
             underlying transport. */
          ssl->got_remote_shutdown = 1;
          gsk_io_notify_read_shutdown (ssl);
          gsk_stream_ssl_shutdown_write (GSK_IO(ssl), error);

          /* XXX: should call SSL_get_error(). */
#if 0
          g_message("ssl_read_rv returned %d: SSL_get_error returns %d",
                    ssl_read_rv, SSL_get_error (ssl->ssl, ssl_read_rv));
#endif
        }
    }
  else if (ssl_read_rv < 0)
    {
      int error_code = SSL_get_error (ssl->ssl, ssl_read_rv);
      switch (error_code)
	{
	case SSL_ERROR_WANT_READ:
	  ssl->write_needed_to_read = 0;
	  break;
	case SSL_ERROR_WANT_WRITE:
	  ssl->write_needed_to_read = 1;
	  break;
	case SSL_ERROR_NONE:
	  g_set_error (error,
		       GSK_G_ERROR_DOMAIN,
		       GSK_ERROR_IO,
		       "error reading from ssl stream, but error code set to none");
	  break;
	case SSL_ERROR_SYSCALL:
	  g_set_error (error,
		       GSK_G_ERROR_DOMAIN, GSK_ERROR_IO,
		       "Gsk-BIO interface had problems reading");
	  break;
	default:
	  {
	    gulong l;
	    l = ERR_peek_error();
	    g_set_error (error,
			 GSK_G_ERROR_DOMAIN,
			 GSK_ERROR_IO,
			 "error reading from ssl stream: %s: %s: %s",
			 ERR_lib_error_string(l),
			 ERR_func_error_string(l),
			 ERR_reason_error_string(l));
	    break;
	  }
	}
      maybe_update_backend_poll_state (ssl);
    }
  return 0;
}

static guint
try_writing_the_write_buffer (GskStreamSsl *ssl, GError **error)
{
  int ssl_write_rv;

  ssl_write_rv = SSL_write (ssl->ssl, ssl->write_buffer, ssl->write_buffer_length);
  if (ssl_write_rv > 0)
    {
      ssl->write_buffer_length -= ssl_write_rv;
      memmove (ssl->write_buffer, ssl->write_buffer + ssl_write_rv, ssl->write_buffer_length);
      return ssl_write_rv;
    }
  if (ssl_write_rv == 0)
    {
      /* TODO: is this right??? :
	 
	 Either a clean shutdown occurred, or an error occurred in the
	 underlying transport. */
      gsk_io_notify_write_shutdown (ssl);

      /* XXX: should call SSL_get_error(). */
    }
  else if (ssl_write_rv < 0)
    {
      int error_code = SSL_get_error (ssl->ssl, ssl_write_rv);
      switch (error_code)
	{
	case SSL_ERROR_WANT_READ:
	  ssl->read_needed_to_write = 1;
	  break;
	case SSL_ERROR_WANT_WRITE:
	  ssl->read_needed_to_write = 0;
	  break;
	case SSL_ERROR_SYSCALL:
	  g_set_error (error,
		       GSK_G_ERROR_DOMAIN, GSK_ERROR_IO,
		       "Gsk-BIO interface had problems writing");
	  break;
	case SSL_ERROR_NONE:
	  g_set_error (error,
		       GSK_G_ERROR_DOMAIN,
		       GSK_ERROR_IO,
		       "error writing to ssl stream, but error code set to none");
	  break;
	default:
	  {
	    gulong l;
	    l = ERR_peek_error();
	    g_set_error (error,
			 GSK_G_ERROR_DOMAIN,
			 GSK_ERROR_IO,
			 "error writing to ssl stream [in the '%s' library]: %s: %s [is-client=%d]",
			 ERR_lib_error_string(l),
			 ERR_func_error_string(l),
			 ERR_reason_error_string(l),
			 ssl->is_client);
	    break;
	  }
	}
      maybe_update_backend_poll_state (ssl);
    }
  return 0;
}

static guint
gsk_stream_ssl_raw_write      (GskStream     *stream,
			       gconstpointer  data,
			       guint          length,
			       GError       **error)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (stream);
  if (length == 0)
    return 0;
  if (ssl->doing_handshake)
    return 0;

  if (ssl->write_buffer_length > 0)
    {
      try_writing_the_write_buffer (ssl, error);
      return 0;
    }

  /* Make sure our buffer is long enough to accomodate the
     amount of data requested.  */
  if (ssl->write_buffer_alloc < length)
    {
      if (ssl->write_buffer_alloc == 0)
	ssl->write_buffer_alloc = 4096;
      while (ssl->write_buffer_alloc < length)
	ssl->write_buffer_alloc *= 2;
      ssl->write_buffer = g_realloc (ssl->write_buffer, ssl->write_buffer_alloc);
    }

  /* Do underlying write. */
  memcpy (ssl->write_buffer, data, length);
  ssl->write_buffer_length = length;
  length = try_writing_the_write_buffer (ssl, error);
  if (*error)
    {
      ssl->write_buffer_length = 0;
      return 0;
    }
  if (length > 0)
    {
      /* On partial success, just throw away the write buffer. */
      ssl->write_buffer_length = 0;
    }
  else if (length == 0)
    {
      /* We will automatically retry this write. */
      length = ssl->write_buffer_length;
    }
  return length;
}

static void
gsk_stream_ssl_finalize (GObject *object)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (object);
  if (ssl->backend != NULL)
    {
      gsk_hook_untrap (gsk_buffer_stream_read_hook (ssl->backend));
      gsk_hook_untrap (gsk_buffer_stream_write_hook (ssl->backend));
      g_object_unref (ssl->backend);
      ssl->backend = NULL;
    }
  if (ssl->transport != NULL)
    {
      g_signal_handlers_disconnect_by_func (ssl->transport, G_CALLBACK (handle_transport_error), ssl);
      g_object_unref (ssl->transport);
    }
  if (ssl->ssl)
    SSL_free (ssl->ssl);
  SSL_CTX_free (ssl->ctx);
  g_free (ssl->read_buffer);
  g_free (ssl->write_buffer);
  g_free (ssl->cert_file);
  g_free (ssl->key_file);
  g_free (ssl->password);
  (*parent_class->finalize) (object);
}

/* --- transport hooks --- */
/* TODO: these should probably handle write_buffer/read_buffer themselves... */
static gboolean
backend_buffered_write_hook (GskStream    *backend,
		     gpointer      data)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (data);
  g_return_val_if_fail (ssl->backend == backend, FALSE);
  if (ssl->doing_handshake)
    {
      GError *error = NULL;
      if (!do_handshake (ssl, ssl->ssl, &error))
	{
	  gsk_io_set_gerror (GSK_IO (ssl), GSK_IO_ERROR_INIT, error);
	  return FALSE;
	}
      return TRUE;
    }
  switch (ssl->state)
    {
    case GSK_STREAM_SSL_STATE_CONSTRUCTING:
      g_return_val_if_reached (FALSE);
    case GSK_STREAM_SSL_STATE_ERROR:
      return FALSE;
    case GSK_STREAM_SSL_STATE_NORMAL:
      if (ssl->backend_poll_write)
	{
	  if (ssl->read_needed_to_write && ssl->this_writable)
	    {
	      ssl->read_needed_to_write = 0;
	      gsk_io_notify_ready_to_write (ssl);
	    }
	  else if (ssl->this_readable)
	    gsk_io_notify_ready_to_read (ssl);
	}
      return TRUE;
    case GSK_STREAM_SSL_STATE_SHUTTING_DOWN:
      {
	GError *error = NULL;
	gsk_stream_ssl_shutdown_both (ssl, &error);
	if (error)
	  gsk_io_set_gerror (GSK_IO (ssl), GSK_IO_ERROR_SHUTDOWN_READ, error);
	return TRUE;
      }
    case GSK_STREAM_SSL_STATE_SHUT_DOWN:
      g_return_val_if_reached (FALSE);
    }
  g_return_val_if_reached (FALSE);
}

static gboolean
backend_buffered_write_shutdown (GskStream    *backend,
		         gpointer      data)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (data);
  g_return_val_if_fail (ssl->backend == backend, FALSE);
  if (ssl->read_buffer_length == 0)
    gsk_io_notify_read_shutdown (ssl);
  return FALSE;
}

static gboolean
backend_buffered_read_hook (GskStream    *backend,
		     gpointer      data)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (data);
  g_return_val_if_fail (ssl->backend == backend, FALSE);
  switch (ssl->state)
    {
    case GSK_STREAM_SSL_STATE_CONSTRUCTING:
      g_return_val_if_reached (FALSE);
    case GSK_STREAM_SSL_STATE_ERROR:
      return FALSE;
    case GSK_STREAM_SSL_STATE_NORMAL:
      if (ssl->doing_handshake)
	{
	  GError *error = NULL;
	  if (!do_handshake (ssl, ssl->ssl, &error))
	    {
	      gsk_io_set_gerror (GSK_IO (ssl), GSK_IO_ERROR_INIT,
				 error);
	      return FALSE;
	    }
	}
      else if (ssl->backend_poll_read)
	{
	  if (ssl->write_needed_to_read && ssl->this_readable)
	    {
	      ssl->write_needed_to_read = 0;
	      gsk_io_notify_ready_to_read (ssl);
	    }
	  else if (ssl->this_writable)
	    gsk_io_notify_ready_to_write (ssl);
	}
      return TRUE;
    case GSK_STREAM_SSL_STATE_SHUTTING_DOWN:
      {
	GError *error = NULL;
	gsk_stream_ssl_shutdown_both (ssl, &error);
	if (error)
	  gsk_io_set_gerror (GSK_IO (ssl), GSK_IO_ERROR_SHUTDOWN_READ, error);
	return ssl->state == GSK_STREAM_SSL_STATE_SHUTTING_DOWN
	    || ssl->state == GSK_STREAM_SSL_STATE_SHUT_DOWN;

      }
    case GSK_STREAM_SSL_STATE_SHUT_DOWN:
      g_return_val_if_reached (FALSE);
    }
  g_return_val_if_reached (FALSE);
}

static gboolean
backend_buffered_read_shutdown (GskStream    *backend,
		          gpointer      data)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (data);
  g_return_val_if_fail (ssl->backend == backend, FALSE);
  return FALSE;
}

static void
gsk_stream_ssl_init (GskStreamSsl *stream_ssl)
{
  SSL_CTX *ctx = SSL_CTX_new (SSLv23_method ());
  SSL_CTX_set_mode (ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
  stream_ssl->ctx = ctx;
  stream_ssl->state = GSK_STREAM_SSL_STATE_CONSTRUCTING;

  gsk_io_mark_is_writable (stream_ssl);
  gsk_io_mark_is_readable (stream_ssl);
  gsk_hook_mark_can_defer_shutdown (GSK_IO_READ_HOOK (stream_ssl));
  gsk_hook_mark_can_defer_shutdown (GSK_IO_WRITE_HOOK (stream_ssl));
}

static void
gsk_stream_ssl_alloc_backend (GskStreamSsl *ssl)
{
  BIO *bio;
  GskBufferStream *backend;
  if (!gsk_openssl_bio_stream_pair (&bio, &backend))
    {
      g_warning ("error making bio-stream pair");
      return;
    }
  ssl->backend = GSK_STREAM (backend);
  SSL_set_bio (ssl->ssl, bio, bio);
  gsk_hook_trap (gsk_buffer_stream_read_hook (backend),
		 (GskHookFunc) backend_buffered_read_hook,
		 (GskHookFunc) backend_buffered_read_shutdown,
		 ssl,
		 NULL);
  gsk_hook_trap (gsk_buffer_stream_write_hook (backend),
		 (GskHookFunc) backend_buffered_write_hook,
		 (GskHookFunc) backend_buffered_write_shutdown,
		 ssl,
		 NULL);
  ssl->backend_poll_read = ssl->backend_poll_write = 1;
  maybe_update_backend_poll_state (ssl);
}

static int
verify_callback (int ok, X509_STORE_CTX *store)
{
  return ok;
}

static void
set_error (GskStreamSsl *ssl,
	   GskIOErrorCause cause,
	   const char *format,
	   ...)
{
  char *msg;
  const char *ssl_error_message;
  const char *ssl_error_filename;
  int ssl_error_lineno;
  int ssl_error_flags;

  va_list args;
  va_start (args, format);
  msg = g_strdup_vprintf (format, args);
  va_end (args);
  if (ERR_get_error_line_data (&ssl_error_filename, &ssl_error_lineno,
			       &ssl_error_message, &ssl_error_flags) == 0)
    {
      ssl_error_filename = "[*unknown*]";
      ssl_error_lineno = -1;
      ssl_error_message = "No SSL error message";
    }
  gsk_io_set_error (GSK_IO (ssl), GSK_IO_ERROR_INIT,
		    GSK_ERROR_BAD_FORMAT,
		    _("error %s: %s [%s, %d]"),
		    msg, ssl_error_message, ssl_error_filename, ssl_error_lineno);
}

/* Initialize the client's SSL context,
   with or without CA support.
   
   Based on [OREILLY], Page 138.
   This corresponds to the function setup_client_ctx(). */
static gboolean
ssl_ctx_setup (GskStreamSsl *ssl)
{
  gboolean verify_ca = (ssl->ca_file != NULL);

  if (verify_ca)
    {
      if (SSL_CTX_load_verify_locations (ssl->ctx, ssl->ca_file, ssl->ca_dir) != 1)
	{
	  set_error (ssl, GSK_ERROR_BAD_FORMAT, "loading CA file or directory");
	  return FALSE;
	}
      if (SSL_CTX_set_default_verify_paths (ssl->ctx) != 1)
	{
	  set_error (ssl, GSK_ERROR_BAD_FORMAT, "loading default CA file and/or directory");
	  return FALSE;
	}
    }

  if (ssl->cert_file != NULL)
    if (SSL_CTX_use_certificate_chain_file (ssl->ctx, ssl->cert_file) != 1)
      {
        set_error (ssl, GSK_ERROR_BAD_FORMAT, "loading certificate from file '%s'",
                   ssl->cert_file);
        return FALSE;
      }
  if (ssl->key_file != NULL)
    if (SSL_CTX_use_PrivateKey_file (ssl->ctx, ssl->key_file, SSL_FILETYPE_PEM) != 1)
      {
        set_error (ssl, GSK_ERROR_BAD_FORMAT, "loading private key from file '%s'",
                   ssl->key_file);
        return FALSE;
      }

  if (verify_ca)
    {
      int verify_flags = SSL_VERIFY_PEER;
      if (!ssl->is_client)
	verify_flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
      SSL_CTX_set_verify (ssl->ctx, verify_flags, verify_callback);
      SSL_CTX_set_verify_depth (ssl->ctx, 4);
    }
  return TRUE;
}

static GObject*
gsk_stream_ssl_constructor    (GType                  type,
			       guint                  n_construct_properties,
			       GObjectConstructParam *construct_properties)
{
  GObject *rv = (*parent_class->constructor) (type, n_construct_properties, construct_properties);
  GskStreamSsl *ssl = GSK_STREAM_SSL (rv);

  if (ssl_ctx_setup (ssl))
    {
      ssl->ssl = SSL_new (ssl->ctx);
      gsk_stream_ssl_alloc_backend (ssl);
      ssl->state = GSK_STREAM_SSL_STATE_NORMAL;
    }
  else
    {
      ssl->state = GSK_STREAM_SSL_STATE_ERROR;
    }
  return rv;
}

static int
gsk_openssl_passwd_cb (char     *buf,
		       int       size,
		       int       is_for_encryption,
		       void     *userdata)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (userdata);
  if (ssl->password != NULL)
    {
      strncpy (buf, ssl->password, size);
      buf[size - 1] = '\0';
      return strlen (ssl->password);
    }
  return 0;
}

void
gsk_stream_ssl_set_property           (GObject        *object,
				       guint           property_id,
				       const GValue   *value,
				       GParamSpec     *pspec)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (object);
  char *arg;

  switch (property_id)
    {
      case PROP_PASSWORD:
	arg = g_value_dup_string (value);
	g_free (ssl->password);
	ssl->password = arg;
	if (ssl->password != NULL)
	  {
	    SSL_CTX_set_default_passwd_cb_userdata (ssl->ctx, ssl);
	    SSL_CTX_set_default_passwd_cb (ssl->ctx, gsk_openssl_passwd_cb);
	  }
	break;
      case PROP_CERT_FILE:
	arg = g_value_dup_string (value);
	g_free (ssl->cert_file);
	ssl->cert_file = arg;
        break;
      case PROP_KEY_FILE:
	arg = g_value_dup_string (value);
	g_free (ssl->key_file);
	ssl->key_file = arg;
        break;
      case PROP_IS_CLIENT:
	g_assert (ssl->ssl == NULL);
	ssl->is_client = g_value_get_boolean (value) ? 1 : 0;
	break;
      default:
	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	break;
    }
}

void
gsk_stream_ssl_get_property	      (GObject        *object,
				       guint           property_id,
				       GValue         *value,
				       GParamSpec     *pspec)
{
  GskStreamSsl *ssl = GSK_STREAM_SSL (object);

  switch (property_id)
    {
      case PROP_CERT_FILE:
	g_value_set_string (value, ssl->cert_file);
        break;
      case PROP_KEY_FILE:
	g_value_set_string (value, ssl->key_file);
        break;
      case PROP_IS_CLIENT:
	g_value_set_boolean (value, ssl->is_client);
        break;
      default:
	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	break;
    }
}

static void
actions_to_seed_PRNG    (void)
{
  while (RAND_status () == 0)
    {
      int i;
      int len;
      unsigned char *buf;

      /* TODO: if we are here, /dev/random doesn't exist ... warezed for now */

      len = 2048;
      buf = g_malloc (len);

      for (i = 0; i < len; i += 4)
        {
          long int h4x;

          h4x = lrand48 ();

          memcpy (buf + i, &h4x, 4);
        }

      RAND_seed (buf, len);

      g_free (buf);
    }
}

static void
gsk_stream_ssl_class_init (GskStreamSslClass *class)
{
  GskIOClass *io_class = GSK_IO_CLASS (class);
  GskStreamClass *stream_class = GSK_STREAM_CLASS (class);
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  static gboolean has_ssl_library_init = FALSE;
  GParamSpec *pspec;
  parent_class = g_type_class_peek_parent (class);
  io_class->set_poll_read = gsk_stream_ssl_set_poll_read;
  io_class->shutdown_read = gsk_stream_ssl_shutdown_read;
  io_class->set_poll_write = gsk_stream_ssl_set_poll_write;
  io_class->shutdown_write = gsk_stream_ssl_shutdown_write;
  stream_class->raw_read = gsk_stream_ssl_raw_read;
  stream_class->raw_write = gsk_stream_ssl_raw_write;
  object_class->constructor = gsk_stream_ssl_constructor;
  object_class->get_property = gsk_stream_ssl_get_property;
  object_class->set_property = gsk_stream_ssl_set_property;
  object_class->finalize = gsk_stream_ssl_finalize;

  if (!has_ssl_library_init)
    {
      //THREAD_setup();
      SSL_load_error_strings ();
      SSL_library_init ();
      actions_to_seed_PRNG ();
    }

  pspec = g_param_spec_string ("key-file",
			       _("Key File"),
			       _("the x.509 PEM Key"),
			       NULL,
			       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  g_object_class_install_property (object_class, PROP_KEY_FILE, pspec);
  pspec = g_param_spec_string ("cert-file",
			       _("Certificate File"),
			       _("the x.509 PEM Certificate"),
			       NULL,
			       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  g_object_class_install_property (object_class, PROP_CERT_FILE, pspec);
  pspec = g_param_spec_boolean ("is-client",
			       _("Is Client"),
			       _("is this a SSL client (versus a server)"),
			       FALSE,
			       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  g_object_class_install_property (object_class, PROP_IS_CLIENT, pspec);

  pspec = g_param_spec_string ("password",
			       _("Password"),
			       _("secret passphrase for the certificate"),
			       NULL,
			       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
  g_object_class_install_property (object_class, PROP_PASSWORD, pspec);
}

GType gsk_stream_ssl_get_type()
{
  static GType stream_ssl_type = 0;
  if (!stream_ssl_type)
    {
      static const GTypeInfo stream_ssl_info =
      {
	sizeof(GskStreamSslClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gsk_stream_ssl_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GskStreamSsl),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gsk_stream_ssl_init,
	NULL		/* value_table */
      };
      stream_ssl_type = g_type_register_static (GSK_TYPE_STREAM,
                                                  "GskStreamSsl",
						  &stream_ssl_info, 0);
    }
  return stream_ssl_type;
}

static GskStreamSsl *
maybe_attach_transport (GskStreamSsl *ssl,
			GskStream    *transport,
			GError      **error)
{
  if (ssl->state == GSK_STREAM_SSL_STATE_ERROR)
    {
      g_propagate_error (error, GSK_IO (ssl)->error);
      g_object_unref (ssl);
      if (transport != NULL)
	g_object_unref (transport);
      return NULL;
    }
  if (transport != NULL)
    {
      GError *suberror = NULL;
      GskStreamConnection *connection;

      ssl->transport = g_object_ref (transport);
      g_signal_connect (transport, "on-error", G_CALLBACK (handle_transport_error), ssl);

      connection = gsk_stream_connection_new (ssl->backend, transport, &suberror);
      if (suberror)
	{
	  g_propagate_error (error, suberror);
	  g_object_unref (ssl);
	  return NULL;
	}
      gsk_stream_attach (transport, ssl->backend, &suberror);
      if (suberror)
	{
	  g_propagate_error (error, suberror);
	  gsk_stream_connection_detach (connection);
	  g_object_unref (connection);
	  g_object_unref (ssl);
	  return NULL;
	}
      g_object_unref (connection);
    }
  return ssl;
}

/**
 * gsk_stream_ssl_new_server:
 * @cert_file: the PEM x509 certificate file.
 * @key_file: key file???
 * @password: password required by the certificate, or NULL.
 * @transport: optional transport layer (which will be connected
 * to the backend stream by bidirectionally).
 * @error: optional location in which to store a #GError.
 *
 * Create a new SSL server.
 * It should be connected to a socket which was accepted from
 * a server (usually provided as the @transport argument).
 *
 * returns: the new SSL stream, or NULL if an error occurs.
 */
GskStream   *gsk_stream_ssl_new_server   (const char   *cert_file,
					  const char   *key_file,
					  const char   *password,
					  GskStream    *transport,
					  GError      **error)
{
  GskStreamSsl *stream_ssl = g_object_new (GSK_TYPE_STREAM_SSL,
				           "is-client", FALSE,
					   "password", password,
				           "cert-file", cert_file,
				           "key-file", key_file,
				           NULL);


  /* This is the OpenSSL library stream type. */
  SSL *ssl;

  stream_ssl = maybe_attach_transport (stream_ssl, transport, error);
  if (stream_ssl == NULL)
    return NULL;

  /* Cast from a gpointer to make the following code
     more typesafe */
  ssl = stream_ssl->ssl;

  SSL_set_accept_state (ssl);
#if 0
  rv = SSL_accept (ssl);
  if (rv <= 0)
    {
      int error_code = SSL_get_error (ssl, rv);
      gulong l = ERR_peek_error();
      switch (error_code)
	{
	case SSL_ERROR_NONE:
	  break;
	case SSL_ERROR_WANT_READ:
	  g_message("accept:want-read");
	  break;
	case SSL_ERROR_WANT_WRITE:
	  g_message("accept:want-write");
	  break;
	default:
	  {
	    g_set_error (error,
			 GSK_G_ERROR_DOMAIN,
			 GSK_ERROR_BAD_FORMAT,
			 _("error accepting on SSL socket: %s: %s [code=%08lx (%lu)]"),
			 ERR_func_error_string(l),
			 ERR_reason_error_string(l),
			 l, l);
	    g_object_unref (stream_ssl);
	    return NULL;
	  }
	}
    }
#endif
  stream_ssl->doing_handshake = 1;
  if (!do_handshake (stream_ssl, ssl, error))
    {
      g_object_unref (stream_ssl);
      return NULL;
    }

  return GSK_STREAM (stream_ssl);
}

/**
 * gsk_stream_ssl_new_client:
 * @cert_file: the PEM x509 certificate file.
 * @key_file: key file???
 * @password: password required by the certificate, or NULL.
 * @transport: optional transport layer (which will be connected
 * to the backend stream by bidirectionally).
 * @error: optional location in which to store a #GError.
 *
 * Create the client end of a SSL connection.
 * This should be attached to a connecting or connected stream,
 * usually provided as the @transport argument.
 *
 * returns: the new SSL stream, or NULL if an error occurs.
 */
GskStream   *gsk_stream_ssl_new_client   (const char   *cert_file,
					  const char   *key_file,
					  const char   *password,
					  GskStream    *transport,
					  GError      **error)
{
  GskStreamSsl *stream_ssl = g_object_new (GSK_TYPE_STREAM_SSL,
				           "is-client", TRUE,
					   "password", password,
				           "cert-file", cert_file,
				           "key-file", key_file,
				           NULL);
  SSL *ssl;
  //GError *suberror = NULL;
  stream_ssl = maybe_attach_transport (stream_ssl, transport, error);
  if (stream_ssl == NULL)
    return NULL;

  /* Cast from a gpointer to make the following code
     more typesafe */
  ssl = stream_ssl->ssl;

  SSL_set_connect_state (ssl);
#if 0
  /* Tell the SSL object to initiate the connection
     protocol.  (XXX: I Think it is correct to say
     that the client takes responsibility for beginning
     the connection, but a reference to a spec or [OREILLY]
     would be good.) */
  if (SSL_connect (ssl) <= 0)
    {
      gulong l = ERR_peek_error();
      g_set_error (error,
		   GSK_G_ERROR_DOMAIN,
		   GSK_ERROR_BAD_FORMAT,
		   _("error initiating SSL connect: %s: %s"),
		   ERR_func_error_string (l),
		   ERR_reason_error_string (l));
      g_object_unref (stream_ssl);
      return NULL;
    }

  g_message ("ran SSL_connect on %p", stream_ssl);
#endif
  stream_ssl->doing_handshake = 1;
  if (!do_handshake (stream_ssl, ssl, error))
    {
      g_object_unref (stream_ssl);
      return NULL;
    }

  return GSK_STREAM (stream_ssl);
}

/**
 * gsk_stream_ssl_peek_backend:
 * @ssl: the stream to query.
 *
 * Get a reference to the backend stream, which
 * should be connected to the underlying transport
 * layer.
 *
 * returns: the SSL backend (to be connected to the transport,
 * which is the stream which is typically insecure
 * without SSL protection).
 */
GskStream *
gsk_stream_ssl_peek_backend (GskStreamSsl *ssl)
{
  return ssl->backend;
}


syntax highlighted by Code2HTML, v. 0.9.1