/***************************************************************************
 *
 * Copyright (c) 2000, 2001 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: giossl.c,v 1.8.4.2 2001/09/19 19:22:16 bazsi Exp $
 *
 * Author  : Buci
 * Auditor : 
 * Last version: 1.0
 * Notes:
 *
 ***************************************************************************/

#include <zorp/giossl.h>

#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <zorp/log.h>

#include <openssl/err.h>

/*
 * SSL IO Channels
 */

typedef struct _GIOSSLWatch GIOSSLWatch;

struct _GIOSSLWatch
{
  GPollFD pollfd;
  GIOChannel *channel;
  GIOCondition condition;
  GIOFunc callback;
};


static GIOError g_io_ssl_read(GIOChannel * channel,
			      gchar * buf,
			      guint count, guint * bytes_written);

static GIOError g_io_ssl_write(GIOChannel * channel,
			       gchar * buf,
			       guint count, guint * bytes_written);
static GIOError g_io_ssl_seek(GIOChannel * channel,
			      gint offset, GSeekType type);
static void g_io_ssl_close(GIOChannel * channel);
static void g_io_ssl_free(GIOChannel * channel);
static guint g_io_ssl_add_watch(GIOChannel * channel,
				gint priority,
				GIOCondition condition,
				GIOFunc func,
				gpointer user_data, GDestroyNotify notify);
static gboolean g_io_ssl_prepare(gpointer source_data,
				 GTimeVal * current_time,
				 gint * timeout, gpointer user_data);
static gboolean g_io_ssl_check(gpointer source_data,
			       GTimeVal * current_time, gpointer user_data);
static gboolean g_io_ssl_dispatch(gpointer source_data,
				  GTimeVal * current_time,
				  gpointer user_data);
static void g_io_ssl_destroy(gpointer source_data);

GSourceFuncs ssl_watch_funcs = {
  g_io_ssl_prepare,
  g_io_ssl_check,
  g_io_ssl_dispatch,
  g_io_ssl_destroy
};

GIOFuncs ssl_channel_funcs = {
  g_io_ssl_read,
  g_io_ssl_write,
  g_io_ssl_seek,
  g_io_ssl_close,
  g_io_ssl_add_watch,
  g_io_ssl_free,
};

static gboolean
g_io_ssl_prepare(gpointer source_data,
		 GTimeVal * current_time, gint * timeout, gpointer user_data)
{
  *timeout = -1;
  return FALSE;
}

static gboolean
g_io_ssl_check(gpointer source_data,
	       GTimeVal * current_time, gpointer user_data)
{
  GIOSSLWatch *data = source_data;

  return (data->pollfd.revents & data->condition);
}

static gboolean
g_io_ssl_dispatch(gpointer source_data,
		  GTimeVal * current_time, gpointer user_data)
{
  GIOSSLWatch *data = source_data;

  return (*data->callback) (data->channel,
			    data->pollfd.revents & data->condition,
			    user_data);
}

static void
g_io_ssl_destroy(gpointer source_data)
{
  GIOSSLWatch *data = source_data;

  g_main_remove_poll(&data->pollfd);
  g_io_channel_unref(data->channel);
  g_free(data);
}

static GIOError
g_io_ssl_read(GIOChannel * channel,
	      gchar * buf, guint count, guint * bytes_read)
{
  GIOSSLChannel *ssl_channel = (GIOSSLChannel *) channel;
  gint result;

  z_enter();

  result = SSL_read(ssl_channel->ssl->ssl, buf, count);

  if (result < 0)
    {
      *bytes_read = 0;
      if (errno == EAGAIN || errno == EINTR)
        {
          z_leave();
          return G_IO_ERROR_AGAIN;
        }
      ERR_error_gstring(ssl_channel->error);
      ERR_clear_error();
      z_leave();
      return G_IO_ERROR_UNKNOWN;
    }
  else
    {
      *bytes_read = result;
      ERR_clear_error();
      z_leave();
      return G_IO_ERROR_NONE;
    }
}

static GIOError
g_io_ssl_write(GIOChannel * channel,
	       gchar * buf, guint count, guint * bytes_written)
{
  GIOSSLChannel *ssl_channel = (GIOSSLChannel *) channel;
  gint result;

  result = SSL_write(ssl_channel->ssl->ssl, buf, count);

  if (result < 0)
    {
      *bytes_written = 0;
      if (errno == EAGAIN || errno == EINTR)
        {
          z_leave();
          return G_IO_ERROR_AGAIN;
        }
      ERR_error_gstring(ssl_channel->error);
      ERR_clear_error();
      return G_IO_ERROR_UNKNOWN;
    }
  else
    {
      *bytes_written = result;
      ERR_clear_error();
      return G_IO_ERROR_NONE;
    }
}

static GIOError
g_io_ssl_seek(GIOChannel * channel, gint offset, GSeekType type)
{
  return G_IO_ERROR_UNKNOWN;
}


static void
g_io_ssl_close(GIOChannel * channel)
{
  GIOSSLChannel *ssl_channel = (GIOSSLChannel *) channel;
  int fd = SSL_get_fd(ssl_channel->ssl->ssl);

  SSL_shutdown(ssl_channel->ssl->ssl);
  ERR_clear_error();
  close(fd);
}

static void
g_io_ssl_free(GIOChannel * channel)
{
  GIOSSLChannel *ssl_channel = (GIOSSLChannel *) channel;

  z_ssl_session_destroy(ssl_channel->ssl);
  ERR_clear_error();
  g_string_free(ssl_channel->error, TRUE);
  g_free(ssl_channel);
}

static guint
g_io_ssl_add_watch(GIOChannel * channel,
		   gint priority,
		   GIOCondition condition,
		   GIOFunc func, gpointer user_data, GDestroyNotify notify)
{
  GIOSSLWatch *watch = g_new(GIOSSLWatch, 1);
  GIOSSLChannel *ssl_channel = (GIOSSLChannel *) channel;

  watch->channel = channel;
  g_io_channel_ref(channel);

  watch->callback = func;
  watch->condition = condition;

  watch->pollfd.fd = SSL_get_fd(ssl_channel->ssl->ssl);
  ERR_clear_error();

  watch->pollfd.events = condition;

  g_main_add_poll(&watch->pollfd, priority);

  return g_source_add(priority, TRUE, &ssl_watch_funcs, watch, user_data,
		      notify);
}

GIOChannel *
g_io_channel_ssl_new(ZSSLSession *ssl)
{
  GIOSSLChannel *ssl_channel;
  GIOChannel *channel;

  g_return_val_if_fail(ssl, NULL);

  ssl_channel = g_new(GIOSSLChannel, 1);
  channel = (GIOChannel *) ssl_channel;

  g_io_channel_init(channel);
  channel->funcs = &ssl_channel_funcs;

  ssl_channel->ssl = ssl;
  ssl_channel->error = g_string_new(NULL);

  return channel;
}

ZSSLSession *
g_io_channel_ssl_get_ssl(GIOChannel * channel)
{
  GIOSSLChannel *ssl_channel = (GIOSSLChannel *) channel;
  return ssl_channel->ssl;
}

gchar *
g_io_channel_ssl_get_error_string(GIOChannel * channel)
{
  GIOSSLChannel *ssl_channel = (GIOSSLChannel *) channel;
  return ssl_channel->error->str;
}

void
ERR_error_gstring(GString * str)
{
  const char *ls, *fs, *rs;
  unsigned long e, l, f, r;

  g_return_if_fail(str);

  e = ERR_get_error();

  l = ERR_GET_LIB(e);
  f = ERR_GET_FUNC(e);
  r = ERR_GET_REASON(e);

  ls = ERR_lib_error_string(e);
  fs = ERR_func_error_string(e);
  rs = ERR_reason_error_string(e);

  g_string_sprintf(str, "error:%08lX:", e);
  if (ls)
    g_string_append(str, ls);
  else
    g_string_sprintfa(str, ":lib(%lu) ", l);
  if (fs)
    g_string_append(str, fs);
  else
    g_string_sprintfa(str, ":func(%lu) ", f);
  if (rs)
    g_string_append(str, rs);
  else
    g_string_sprintfa(str, ":reason(%lu) ", r);
}
