/***************************************************************************
 *
 * Copyright (c) 2000 BalaBit IT Ltd, Budapest, Hungary
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: pssl.c,v 1.9.2.3.2.3 2001/09/19 19:38:47 bazsi Exp $
 *
 ***************************************************************************/

#include <zorp/stream.h>
#include <zorp/proxy.h>
#include <zorp/poll.h>
#include <zorp/policy.h>
#include <zorp/thread.h>
#include <zorp/log.h>
#include <zorp/registry.h>
#include <zorp/sockaddr.h>
#include <zorp/io.h>
#include <zorp/giossl.h>
#include <zorp/ssl.h>

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <openssl/err.h>

#define ENABLE_PKT_STATS 1

#define PROXY_BUFSIZE    1500	/* 64k */

#define PSSL_DEBUG "pssl.debug"
#define PSSL_ERROR "pssl.error"

typedef struct _ZIOBuffer
{
  gchar buf[PROXY_BUFSIZE];
  guint ofs, end;
  guint packet_count, packet_bytes;
} ZIOBuffer;

#define EOF_CLIENT         0x0001
#define EOF_SERVER         0x0002
#define EOF_STACKED_CLIENT 0x0004
#define EOF_STACKED_SERVER 0x0008

typedef struct _PsslProxy
{
  ZProxy super;
  ZStackedProxy *stacked;
  ZIOBuffer buffers[EP_MAX];
  ZIOBuffer *downbufs[EP_MAX];
  ZSockAddr *addr;
  ZPoll *poll;
  gint eofmask;
  gint timeout;
  gint packet_stats_interval;
  guint global_packet_count;
  gboolean copy_to_server, copy_to_client;
  GString *client_keyfile, *client_certfile;
  GString *server_keyfile, *server_certfile;
  GString *client_ca_dir;
  GString *client_crl_dir;
  GString *server_ca_dir;
  GString *server_crl_dir;
  gboolean need_ssl[EP_MAX];
  struct timeval started_time;
  SSL_CTX *client_ctx, *server_ctx;
  int verify_type[EP_MAX];
  int verify_depth[EP_MAX];
} PsslProxy;

typedef ZStream ZRealStream;

static void pssl_proxy_free(ZProxy * s);

static void
pssl_config_set_defaults(PsslProxy *self)
{
  gint i;
  
  self->client_keyfile = g_string_sized_new(32);
  self->client_certfile = g_string_sized_new(32);

  self->client_ca_dir = g_string_sized_new(0);
  self->client_crl_dir = g_string_sized_new(0);

  self->server_ca_dir = g_string_sized_new(32);
  self->server_crl_dir = g_string_sized_new(32);

  self->server_keyfile = g_string_sized_new(0);
  self->server_certfile = g_string_sized_new(0);
  self->timeout = -1;
  
  self->verify_type[EP_SERVER] = Z_SSL_VERIFY_NONE;
  self->verify_type[EP_CLIENT] = Z_SSL_VERIFY_NONE;
  self->verify_depth[EP_SERVER] = 1;
  self->verify_depth[EP_CLIENT] = 1;

  self->copy_to_server = 1;
  self->copy_to_client = 1;
  self->client_ctx = NULL;
  self->server_ctx = NULL;
  for (i = 0; i < EP_MAX; i++)
    {
      self->need_ssl[i] = 0;
    }
  gettimeofday(&self->started_time, NULL);
  self->poll = z_poll_new();

}

static void
pssl_register_vars(PsslProxy *self)
{

  z_proxy_var_new(&self->super, "copy_to_client",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
		  &self->copy_to_client);
  z_proxy_var_new(&self->super, "copy_to_server",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
		  &self->copy_to_server);
  z_proxy_var_new(&self->super, "packet_stats_interval",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
		  &self->packet_stats_interval);

  z_proxy_var_new(&self->super, "timeout",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
		  &self->timeout);
		  
  z_proxy_var_new(&self->super, "client_need_ssl",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
		  &self->need_ssl[EP_CLIENT]);
  z_proxy_var_new(&self->super, "client_key_file",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
		  self->client_keyfile);
  z_proxy_var_new(&self->super, "client_cert_file",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
		  self->client_certfile);
  z_proxy_var_new(&self->super, "client_ca_directory",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
                  self->client_ca_dir);
  z_proxy_var_new(&self->super, "client_crl_directory",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
                  self->client_crl_dir);
  z_proxy_var_new(&self->super, "client_verify_type",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
                  &self->verify_type[EP_CLIENT]);
  z_proxy_var_new(&self->super, "client_verify_depth",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
                  &self->verify_depth[EP_CLIENT]);


  z_proxy_var_new(&self->super, "server_need_ssl",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
		  &self->need_ssl[EP_SERVER]);
  z_proxy_var_new(&self->super, "server_key_file",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
		  self->server_keyfile);
  z_proxy_var_new(&self->super, "server_cert_file",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
		  self->server_certfile);
  z_proxy_var_new(&self->super, "server_ca_directory",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
                  self->server_ca_dir);
  z_proxy_var_new(&self->super, "server_crl_directory",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING,
                  self->server_crl_dir);
  z_proxy_var_new(&self->super, "server_verify_type",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
                  &self->verify_type[EP_SERVER]);
  z_proxy_var_new(&self->super, "server_verify_depth",
                  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT,
                  &self->verify_depth[EP_SERVER]);

  /* compatibility aliases with Zorp 0.8.x */
  z_proxy_var_new(&self->super, "client_key",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_ALIAS,
		  "client_key_file");
  z_proxy_var_new(&self->super, "client_cert",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_ALIAS,
		  "client_cert_file");
  z_proxy_var_new(&self->super, "server_key",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_ALIAS,
		  "server_key_file");
  z_proxy_var_new(&self->super, "server_cert",
		  Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_ALIAS,
		  "server_cert_file");
}

static guint
pssl_read_input(PsslProxy *self, ZStream *input, ZIOBuffer *buf)
{
  gint rc;
  gboolean called;

  z_proxy_enter(self);
  rc = z_stream_read(input, buf->buf, sizeof(buf->buf), &buf->end);
  switch (rc)
    {
    case G_IO_ERROR_NONE:
      buf->packet_bytes += buf->end;
      buf->packet_count++;
      self->global_packet_count++;
      if (self->packet_stats_interval &&
	  (self->global_packet_count % self->packet_stats_interval) == 0)
	{
	  int i;
	  ZPolicyObj *res;

	  z_policy_lock(self->super.thread);
	  for (i = EP_CLIENT; i < EP_MAX; i++)
	    {
	      ZIOBuffer *buf;
	      struct timeval now, spent;
	      char *varname[EP_MAX] =
		{ "bandwidth_to_client", "bandwidth_to_server" };

	      buf = &self->buffers[i];
	      gettimeofday(&now, NULL);
	      spent.tv_sec = now.tv_sec - self->started_time.tv_sec;
	      spent.tv_usec = now.tv_usec - self->started_time.tv_usec;
	      if (spent.tv_usec < -500000)
		{
		  spent.tv_sec++;
		}
	      z_policy_setattr(self->super.handler, varname[i],
			       z_policy_var_build("d",
						  ((double) buf->
						   packet_bytes) /
						  spent.tv_sec));
	    }
	  res =
	    z_policy_call(self->super.handler, "packetStats",
			   z_policy_var_build("iiii",
					      self->buffers[EP_CLIENT].
					      packet_bytes,
					      self->buffers[EP_CLIENT].
					      packet_count,
					      self->buffers[EP_SERVER].
					      packet_bytes,
					      self->buffers[EP_SERVER].
					      packet_count),
			   &called);
	  if (res)
	    {
              if (!z_policy_var_parse(res, "i", &rc))
                rc = Z_DENY;
            }
          else
            if (called)
              rc = Z_DENY;
          z_policy_var_unref(res);
	  z_policy_unlock(self->super.thread);
	  if (rc != Z_ACCEPT)
	    {
	      z_proxy_leave(self);
	      return ST_CLOSE | ST_EOF;
	    }
	}
      if (buf->end == 0)
	{
	  z_proxy_leave(self);
	  return ST_CLOSE | ST_EOF;
	}
      z_proxy_leave(self);
      return ST_OK | ST_GOON;
    case G_IO_ERROR_AGAIN:
      z_proxy_leave(self);
      return ST_AGAIN | ST_GOON;
    default:
      z_proxy_leave(self);
      return ST_ERR | ST_CLOSE;
    }
  z_proxy_leave(self);
}

static guint
pssl_write_output(PsslProxy *self, ZIOBuffer *buf, ZStream *output)
{
  int rc;
  guint bytes_written;

  z_proxy_enter(self);
  if (buf->ofs != buf->end)
    {
      /* buffer not empty */
      rc =
	z_stream_write(output, &buf->buf[buf->ofs], buf->end - buf->ofs,
		       &bytes_written);
      switch (rc)
	{
	case G_IO_ERROR_NONE:
	  buf->ofs += bytes_written;
	  break;
	case G_IO_ERROR_AGAIN:
	  break;
	default:
	  z_proxy_leave(self);
	  return ST_ERR | ST_CLOSE;
	}
      if (buf->ofs != buf->end)
	{
	  output->want_write = TRUE;
	  z_proxy_leave(self);
	  return ST_AGAIN | ST_GOON;
	}
      else
        {
          z_proxy_leave(self);
	  return ST_OK | ST_GOON;
	}
    }
  z_proxy_leave(self);
  return ST_OK | ST_GOON;
}

static guint
pssl_copy_data(PsslProxy *self, ZStream *from, ZStream *to,
	       ZIOBuffer *buf)
{
  int rc;
  int pkt_count = 0;

  z_proxy_enter(self);
  from->want_read = FALSE;
  if (to)
    {
      to->want_write = FALSE;
      rc = pssl_write_output(self, buf, to);
      if (ST_ERROR(rc) != ST_OK)
        {
          z_proxy_leave(self);
	  return rc;
	}
    }
  while (1)
    {
      buf->ofs = buf->end = 0;
      rc = pssl_read_input(self, from, buf);
      if (ST_ERROR(rc) == ST_OK)
	{
	  if (to)
	    {
	      rc = pssl_write_output(self, buf, to);
	      if (ST_ERROR(rc) == ST_AGAIN)
		break;
	      else if (ST_FAILED(rc))
		{
		  z_proxy_log(self, PSSL_ERROR, 1, "Error writing %s (%m)",
			    to->name);
                  z_proxy_leave(self);
		  return ST_QUIT | ST_OK;
		}
	    }
	}
      else if (ST_ERROR(rc) == ST_AGAIN)
	break;
      else if (ST_ERROR(rc) == ST_EOF)
        {
          z_proxy_leave(self);
	  return ST_EOF | ST_GOON;
	}
      else if (ST_FAILED(rc))
	{
	  z_proxy_log(self, PSSL_ERROR, 1, "Error reading %s (%m)",
		    from->name);
          z_proxy_leave(self);
	  return ST_QUIT | ST_OK;
	}
      pkt_count++;
    }
  if (buf->ofs == buf->end)
    {
      from->want_read = TRUE;
    }
  z_proxy_leave(self);
  return ST_OK | ST_GOON;
}

/* callbacks when no stacking is made */
static guint
pssl_copy_client_to_server(ZStream *stream, GIOCondition cond)
{
  PsslProxy *self = (PsslProxy *) stream->user_data;
  guint ret;

  z_proxy_enter(self);
  if (self->copy_to_server)
    ret = pssl_copy_data(self, self->super.endpoints[EP_CLIENT],
			  self->super.endpoints[EP_SERVER],
			  &self->buffers[EP_SERVER]);
  else
    ret = pssl_copy_data(self, self->super.endpoints[EP_CLIENT], NULL,
			  &self->buffers[EP_SERVER]);
  if (ST_ERROR(ret) == ST_EOF)
    {
      ret = ST_QUIT | ST_OK;
    }
  z_proxy_leave(self);
  return ret;
}

static guint
pssl_copy_server_to_client(ZStream *stream, GIOCondition cond)
{
  PsslProxy *self = (PsslProxy *) stream->user_data;
  guint ret;

  z_proxy_enter(self);
  if (self->copy_to_client)
    ret = pssl_copy_data(self, self->super.endpoints[EP_SERVER],
			  self->super.endpoints[EP_CLIENT],
			  &self->buffers[EP_CLIENT]);
  else
    ret = pssl_copy_data(self, self->super.endpoints[EP_SERVER], NULL,
			  &self->buffers[EP_CLIENT]);

  if (ST_ERROR(ret) == ST_EOF)
    {
      ret = ST_QUIT | ST_OK;
    }

  z_proxy_leave(self);
  return ret;
}

/* callbacks when a stacked module exists */
static guint
pssl_copy_client_to_down(ZStream *stream, GIOCondition cond)
{
  PsslProxy *self = (PsslProxy *) stream->user_data;
  guint ret;

  z_proxy_enter(self);
  if (self->copy_to_server)
    ret = pssl_copy_data(self, self->super.endpoints[EP_CLIENT],
			  self->stacked->downstreams[EP_CLIENT],
			  self->downbufs[EP_CLIENT]);
  else
    ret = pssl_copy_data(self, self->super.endpoints[EP_CLIENT], NULL,
			  self->downbufs[EP_CLIENT]);

  if (ST_ERROR(ret) == ST_EOF)
    {
      z_stream_shutdown(self->super.endpoints[EP_CLIENT], SHUT_RD);
      z_stream_shutdown(self->stacked->downstreams[EP_CLIENT], SHUT_WR);
      self->eofmask |= EOF_CLIENT;
      ret = ST_GOON | ST_OK;
    }

  z_proxy_leave(self);
  return ret;
}

static guint
pssl_copy_down_to_client(ZStream *stream, GIOCondition cond)
{
  PsslProxy *self = (PsslProxy *) stream->user_data;
  guint ret;

  z_proxy_enter(self);
  ret = pssl_copy_data(self, self->stacked->downstreams[EP_CLIENT],
			self->super.endpoints[EP_CLIENT],
			&self->buffers[EP_CLIENT]);

  if (ST_ERROR(ret) == ST_EOF)
    {
      z_stream_shutdown(self->stacked->downstreams[EP_CLIENT], SHUT_RD);
      z_stream_shutdown(self->super.endpoints[EP_CLIENT], SHUT_WR);
      self->eofmask |= EOF_STACKED_CLIENT;
      if ((self->eofmask & (EOF_STACKED_CLIENT + EOF_STACKED_SERVER)) == (EOF_STACKED_CLIENT + EOF_STACKED_SERVER))
        ret = ST_QUIT | ST_OK;
      else
        ret = ST_GOON | ST_OK;
    }

  z_proxy_leave(self);
  return ret;

}

static guint
pssl_copy_server_to_down(ZStream *stream, GIOCondition cond)
{
  PsslProxy *self = (PsslProxy *) stream->user_data;
  guint ret;

  z_proxy_enter(self);
  if (self->copy_to_client)
    ret = pssl_copy_data(self, self->super.endpoints[EP_SERVER],
			  self->stacked->downstreams[EP_SERVER],
			  self->downbufs[EP_SERVER]);
  else
    ret = pssl_copy_data(self, self->super.endpoints[EP_SERVER], NULL,
			  self->downbufs[EP_SERVER]);

  if (ST_ERROR(ret) == ST_EOF)
    {
      z_stream_shutdown(self->super.endpoints[EP_SERVER], SHUT_RD);
      z_stream_shutdown(self->stacked->downstreams[EP_SERVER], SHUT_WR);
      self->eofmask |= EOF_SERVER;
      ret = ST_GOON | ST_OK;
    }
  z_proxy_leave(self);
  return ret;
}

static guint
pssl_copy_down_to_server(ZStream *stream, GIOCondition cond)
{
  PsslProxy *self = (PsslProxy *) stream->user_data;
  guint ret;

  z_proxy_enter(self);
  ret = pssl_copy_data(self, self->stacked->downstreams[EP_SERVER],
			self->super.endpoints[EP_SERVER],
			&self->buffers[EP_SERVER]);

  if (ST_ERROR(ret) == ST_EOF)
    {
      z_stream_shutdown(self->stacked->downstreams[EP_SERVER], SHUT_RD);
      z_stream_shutdown(self->super.endpoints[EP_SERVER], SHUT_WR);
      self->eofmask |= EOF_STACKED_SERVER;
      if ((self->eofmask & (EOF_STACKED_CLIENT + EOF_STACKED_SERVER)) == (EOF_STACKED_CLIENT + EOF_STACKED_SERVER))
        ret = ST_QUIT | ST_OK;
      else
        ret = ST_GOON | ST_OK;
    }

  z_proxy_leave(self);
  return ret;
}

guint
pssl_init_client_ssl(PsslProxy *self)
{
  ZSSLSession *ssl;
  X509 *peercert;
  int ret;
  
  z_proxy_enter(self);
  
  ssl = z_ssl_session_new(self->super.session_id, 
                          Z_SSL_MODE_SERVER,
                          self->client_keyfile->str,
                          self->client_certfile->str,
                          self->client_ca_dir->str,
                          self->client_crl_dir->str,
                          self->verify_depth[EP_CLIENT],
                          self->verify_type[EP_CLIENT]);
  if (!ssl)
    {
      z_proxy_log(self, PSSL_ERROR, 1, "Error initializing SSL session on the client side;");
      z_proxy_leave(self);
      return FALSE;
    }

  SSL_set_options(ssl->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
	
  SSL_set_fd(ssl->ssl, self->super.endpoints[EP_CLIENT]->fd);
  ret = SSL_accept(ssl->ssl);
  if (ret < 0)
    {
      char buf[1024];
      
      z_proxy_log(self, PSSL_ERROR, 1, "SSL handshake failed on the client side; error='%s'", z_ssl_get_error_str(buf, sizeof(buf)));
      z_proxy_leave(self);
      return FALSE;
    }
  
  peercert = SSL_get_peer_certificate(ssl->ssl);
  if (peercert)
    {
      gchar tmp[1024];
      
      X509_NAME_oneline(X509_get_subject_name(peercert), tmp, sizeof(tmp) - 1);
      X509_free(peercert);
      z_proxy_log(self, PSSL_DEBUG, 4, "Identified peer on the client side; peer='%s'", tmp);
    }
  
  z_proxy_log(self, PSSL_DEBUG, 6, "Client side SSL handshake successful;");
  z_stream_ssl(self->super.endpoints[EP_CLIENT], ssl);
  z_proxy_leave(self);
  return TRUE;
}

guint
pssl_init_server_ssl(PsslProxy *self)
{
  ZSSLSession *ssl;
  X509 *peercert;
  int ret;
  
  z_proxy_enter(self);
  
  ssl = z_ssl_session_new(self->super.session_id, 
                          Z_SSL_MODE_CLIENT,
                          self->server_keyfile->str,
                          self->server_certfile->str,
                          self->server_ca_dir->str,
                          self->server_crl_dir->str,
                          self->verify_depth[EP_SERVER],
                          self->verify_type[EP_SERVER]);
  if (!ssl)
    {
      z_proxy_log(self, PSSL_ERROR, 1, "Error initializing SSL session on the server side;");
      z_proxy_leave(self);
      return FALSE;
    }
	
  SSL_set_options(ssl->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);

  SSL_set_fd(ssl->ssl, self->super.endpoints[EP_SERVER]->fd);
  ret = SSL_connect(ssl->ssl);
  if (ret < 0)
    {
      char buf[1024];
      
      z_proxy_log(self, PSSL_ERROR, 1, "SSL handshake failed on the server side; error='%s'", z_ssl_get_error_str(buf, sizeof(buf)));
      z_proxy_leave(self);
      return FALSE;
    }
  
  peercert = SSL_get_peer_certificate(ssl->ssl);
  if (peercert)
    {
      gchar tmp[1024];
      
      X509_NAME_oneline(X509_get_subject_name(peercert), tmp, sizeof(tmp));
      X509_free(peercert);
      z_proxy_log(self, PSSL_DEBUG, 4, "Identified peer on the server side; peer='%s'", tmp);
    }
  
  z_proxy_log(self, PSSL_DEBUG, 6, "Server side SSL handshake successful;");
  z_stream_ssl(self->super.endpoints[EP_SERVER], ssl);
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
pssl_init_streams(PsslProxy *self)
{
  z_proxy_enter(self);  
  if (!self->super.endpoints[EP_CLIENT] || 
      !self->super.endpoints[EP_SERVER] ||
      !self->poll)
    {
      z_proxy_leave(self);
      return FALSE;
    }

  if (self->need_ssl[EP_CLIENT])
    {
      z_proxy_log(self, PSSL_DEBUG, 6, "Client needs ssl.");
      if (!pssl_init_client_ssl(self))
        {
          z_proxy_leave(self);
          return FALSE;
        }
    }
  if (self->need_ssl[EP_SERVER])
    {
      z_proxy_log(self, PSSL_DEBUG, 6, "Server needs ssl.");
      if (!pssl_init_server_ssl(self))
        {
          z_proxy_leave(self);
          return FALSE;
        }
    }

  z_fd_set_nonblock(self->super.endpoints[EP_CLIENT]->fd, 1);
  self->super.endpoints[EP_CLIENT]->read = pssl_copy_client_to_server;
  self->super.endpoints[EP_CLIENT]->write = pssl_copy_server_to_client;
  self->super.endpoints[EP_CLIENT]->user_data = self;
  self->super.endpoints[EP_CLIENT]->want_read = TRUE;

  z_fd_set_nonblock(self->super.endpoints[EP_SERVER]->fd, 1);
  self->super.endpoints[EP_SERVER]->read = pssl_copy_server_to_client;
  self->super.endpoints[EP_SERVER]->write = pssl_copy_client_to_server;
  self->super.endpoints[EP_SERVER]->user_data = self;
  self->super.endpoints[EP_SERVER]->want_read = TRUE;

  z_poll_add_stream(self->poll, self->super.endpoints[EP_CLIENT]);
  z_poll_add_stream(self->poll, self->super.endpoints[EP_SERVER]);
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
pssl_init_stacked_streams(PsslProxy *self)
{
  if (self->stacked)
    {
      self->downbufs[EP_CLIENT] = g_new0(ZIOBuffer, 1);
      self->downbufs[EP_SERVER] = g_new0(ZIOBuffer, 1);

      self->super.endpoints[EP_CLIENT]->read = pssl_copy_client_to_down;
      self->super.endpoints[EP_CLIENT]->write = pssl_copy_down_to_client;

      self->super.endpoints[EP_SERVER]->read = pssl_copy_server_to_down;
      self->super.endpoints[EP_SERVER]->write = pssl_copy_down_to_server;

      self->stacked->downstreams[EP_CLIENT]->read = pssl_copy_down_to_client;
      self->stacked->downstreams[EP_CLIENT]->write = pssl_copy_client_to_down;
      self->stacked->downstreams[EP_CLIENT]->user_data = self;
      self->stacked->downstreams[EP_CLIENT]->want_read = TRUE;

      self->stacked->downstreams[EP_SERVER]->read = pssl_copy_down_to_server;
      self->stacked->downstreams[EP_SERVER]->write = pssl_copy_server_to_down;
      self->stacked->downstreams[EP_SERVER]->user_data = self;
      self->stacked->downstreams[EP_SERVER]->want_read = TRUE;

      z_poll_add_stream(self->poll, self->stacked->downstreams[EP_CLIENT]);
      z_poll_add_stream(self->poll, self->stacked->downstreams[EP_SERVER]);
    }
  return TRUE;
}

static gboolean
pssl_request_stack_event(PsslProxy * self)
{
  ZPolicyObj *res;
  gboolean called, rc = TRUE;

  z_proxy_enter(self);
  z_policy_lock(self->super.thread);
  res = z_policy_call(self->super.handler, "requestStack", NULL, &called);
  if (res)
    {
      if (res != z_policy_none)
        self->stacked = z_proxy_stack_proxy(&self->super, res);
    }
  else
    if (called)
      rc = FALSE;
  z_policy_var_unref(res);
  z_policy_unlock(self->super.thread);
  z_proxy_leave(self);
  return rc;
}

static gboolean
pssl_main(PsslProxy *self)
{
  z_proxy_enter(self);
  
  ZPROXY_SET_STATE(self, PS_CONFIG);
  if (!z_proxy_config_event(&self->super))
    {
      z_proxy_leave(self);
      return FALSE;
    }

  ZPROXY_SET_STATE(self, PS_STARTING_UP);
  if (!z_proxy_startup_event(&self->super))
    {
      z_proxy_leave(self);
      return FALSE;
    }

  /* this sets the server side endpoint if successful */
  if (!z_proxy_connect_server_event(&self->super, NULL, 0))
    {
      z_proxy_leave(self);
      return FALSE;
    }
    
  
  if (!pssl_init_streams(self))
    {
      z_proxy_leave(self);
      return FALSE;
    }

  if (!pssl_request_stack_event(self))
    {
      z_proxy_leave(self);
      return FALSE;
    }
    
  if (!pssl_init_stacked_streams(self))
    {
      z_proxy_leave(self);
      return FALSE;
    }
    
  ZPROXY_SET_STATE(self, PS_WORKING);
  while (!(ZPROXY_GET_FLAG(self, PF_QUIT)) && z_poll_iter_timeout(self->poll, self->timeout))
    {
      ;
    }

  ZPROXY_SET_STATE(self, PS_SHUTTING_DOWN);
  z_proxy_shutdown_event(&self->super);
  return TRUE;
}

static void
pssl_thread(gpointer s)
{
  PsslProxy *self = (PsslProxy *) s;

  z_proxy_enter(self);
  
  pssl_config_set_defaults(self);
  pssl_register_vars(self);
  
  pssl_main(self);
  
  z_proxy_destroy(&self->super);
  z_leave();
}

static ZProxy *
pssl_proxy_new(gchar *session_id, ZStream *client, ZPolicyObj *handler)
{
  PsslProxy *self = g_new0(PsslProxy, 1);

  z_proxy_init(&self->super, session_id, client, handler);
  z_proxy_enter(self);
	  
  self->super.free = pssl_proxy_free;
  z_thread_new(session_id, pssl_thread, self);
  z_proxy_leave(self);
  return &self->super;
}

static void
pssl_proxy_free(ZProxy * s)
{
  PsslProxy *self = (PsslProxy *) s;

  z_proxy_enter(self);
  if (self->stacked)
    {
      g_free(self->downbufs[EP_CLIENT]);
      g_free(self->downbufs[EP_SERVER]);
      z_stacked_proxy_destroy(self->stacked);
    }
/*
  g_string_free(self->client_keyfile, TRUE);
  g_string_free(self->client_certfile, TRUE);
  g_string_free(self->server_keyfile, TRUE);
  g_string_free(self->server_certfile, TRUE);
*/

  if (self->poll)
    {
      z_poll_unref(self->poll);
      self->poll = NULL;
    }

  if (self->server_ctx)
    {
      SSL_CTX_free(self->server_ctx);
    }
  if (self->client_ctx)
    {
      SSL_CTX_free(self->client_ctx);
    }
  ERR_remove_state(0);
  z_proxy_leave(self);
}

gint
zorp_module_init(void)
{
  z_registry_add("pssl", ZR_SPROXY, pssl_proxy_new);
  return TRUE;
}
