/***************************************************************************
 *
 * 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: whois.c,v 1.3.4.3 2001/09/19 19:38:43 bazsi Exp $
 *
 ***************************************************************************/
#include <zorp/zorp.h>
#include <zorp/readline.h>
#include <zorp/proxy.h>
#include <zorp/thread.h>
#include <zorp/registry.h>
#include <zorp/log.h>
#include <zorp/policy.h>

#include <ctype.h>

/* log classes used by this module */

#define WHOIS_DEBUG "whois.debug"
#define WHOIS_ERROR "whois.error"
#define WHOIS_POLICY "whois.policy"
#define WHOIS_REQUEST "whois.request"

/*+

  State information of the Whois proxy.

  +*/
typedef struct _WhoisProxy
{
  ZProxy super;
  ZReadLine *readline[EP_MAX];
  gint timeout;
  gint max_request_length;
  gint max_line_length;
  GString *request;
  GString *response_header;
  GString *response_footer;
} WhoisProxy;

/*+

  Fill in our state with default values.

  +*/
static void
whois_config_set_defaults(WhoisProxy *self)
{
  z_proxy_enter(self);
  self->max_request_length = 128;
  self->max_line_length = 132;
  self->request = g_string_sized_new(0);
  self->response_header = g_string_sized_new(0);
  self->response_footer = g_string_sized_new(0);
  z_proxy_leave(self);
}

/*+

  Register variables exported to the policy layer.

  +*/
static void
whois_register_vars(WhoisProxy *self)
{
  z_proxy_enter(self);
  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, "max_line_length", Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT, &self->max_line_length);
  z_proxy_var_new(&self->super, "max_request_length", Z_VAR_GET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_INT, &self->max_request_length);
  z_proxy_var_new(&self->super, "request", Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING, self->request);
  z_proxy_var_new(&self->super, "response_header", Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING, self->response_header);
  z_proxy_var_new(&self->super, "response_footer", Z_VAR_GET | Z_VAR_SET | Z_VAR_SET_CONFIG | Z_VAR_TYPE_STRING, self->response_footer);
  z_proxy_leave(self);
}

/*+

  Initialize config that python gave us. For now it's empty.

  +*/
static void
whois_config_init(WhoisProxy *self)
{
  /* should initialize self based on settings previously set by the config event handler */
}

/*+

  Initialize our client stream. We allocate a readline instance so 
  that we can fetch input line by line.

  +*/
static gboolean
whois_init_client_stream(WhoisProxy *self)
{
  z_proxy_enter(self);
  self->readline[EP_CLIENT] = z_read_line_new(self->super.endpoints[EP_CLIENT], self->max_line_length, ZRL_EOL_CRLF);
  z_proxy_leave(self);
  return TRUE;
}

/*+

  Initialize our server stream. Exit with an error if our server side
  is not connected. (ie NULL)

  +*/
static gboolean
whois_init_server_stream(WhoisProxy *self)
{
  z_proxy_enter(self);
  if (!self->super.endpoints[EP_SERVER])
    {
      z_proxy_leave(self);
      return FALSE;
    }
  self->readline[EP_SERVER] = z_read_line_new(self->super.endpoints[EP_SERVER], self->max_line_length, ZRL_EOL_CRLF);
  z_proxy_leave(self);
  return TRUE;
}

/*+ 

  Read and process a request.
  
  +*/
static gboolean
whois_fetch_request(WhoisProxy *self)
{
  gchar *line;
  gint line_length, res;

  z_proxy_enter(self);  
  res = z_read_line_get(self->readline[EP_CLIENT], &line, &line_length);
  if (ST_ERROR(res) != ST_OK)
    {
      z_proxy_log(self, WHOIS_ERROR, 1, "NULL request");
      z_proxy_leave(self);
      return FALSE;
    }
    
  z_proxy_log(self, WHOIS_REQUEST, 7, "request, line=%.*s", line_length, line);
  
  if (line_length > self->max_request_length)
    {
      z_proxy_log(self, WHOIS_REQUEST, 6, "request too long, length=%d, permitted=%d", line_length, self->max_request_length);
      z_proxy_leave(self);
      return FALSE;
    }
  
  g_string_truncate(self->request, 0);
  g_string_append_len(self->request, line, line_length);
  z_proxy_leave(self);
  return TRUE;
}

/*+

  Construct and send a request to the server based on the state
  stored by whois_fetch_request().

  +*/
static gboolean
whois_send_request(WhoisProxy *self)
{
  gchar request[self->request->len + 6];
  gint bytes_written;
  
  z_proxy_enter(self);
  g_snprintf(request, sizeof(request), "%s\r\n", self->request->str);
    
  if (z_stream_write(self->super.endpoints[EP_SERVER], request, strlen(request), &bytes_written) != G_IO_ERROR_NONE)
    {
      z_proxy_leave(self);
      return FALSE;
    }
  z_proxy_leave(self);
  return TRUE;
}

/*+
 
  Copy server's response to the client.

  +*/
static gboolean
whois_copy_response(WhoisProxy *self)
{
  gint bytes_written;
  
  z_proxy_enter(self);
  if (self->response_header->len &&
      z_stream_write(self->super.endpoints[EP_CLIENT], self->response_header->str, self->response_header->len, &bytes_written) != G_IO_ERROR_NONE)
    {
      z_proxy_leave(self);
      return FALSE;
    }

  while (1)
    {
      gchar *line;
      gint line_len, res;
      gchar *response;
      
      res = z_read_line_get(self->readline[EP_SERVER], &line, &line_len);
      if (ST_ERROR(res) != ST_OK)
        /* EOF or read error */
        break;

      response = alloca(line_len + 3);
      memcpy(response, line, line_len);
      strcpy(response + line_len, "\r\n");

      if (z_stream_write(self->super.endpoints[EP_CLIENT], response, line_len + 2, &bytes_written) != G_IO_ERROR_NONE)
        {
          z_proxy_leave(self);
          return FALSE;
        }

    }
  if (self->response_footer->len &&
      z_stream_write(self->super.endpoints[EP_CLIENT], self->response_footer->str, self->response_footer->len, &bytes_written) != G_IO_ERROR_NONE)
    {
      z_proxy_leave(self);
      return FALSE;
    }
  z_proxy_leave(self);
  return TRUE;
}

static gboolean
whois_query_policy(WhoisProxy *self)
{
  char *errmsg = "Policy violation, request denied.\r\n";
  gint bytes_written;
  gint res;

  z_proxy_enter(self);  
  z_policy_lock(self->super.thread);
  res = z_policy_event(self->super.handler, "whoisRequest", z_policy_var_build("(s)", self->request->str));
  switch (res)
    {
    case Z_DROP:
    case Z_REJECT:
    case Z_ABORT:
      
      z_stream_write(self->super.endpoints[EP_CLIENT], errmsg, strlen(errmsg), &bytes_written);
      /* fallthrough */
      
    case Z_DENY:
      z_policy_unlock(self->super.thread);
      z_proxy_leave(self);
      return FALSE;
    case Z_UNSPEC:
    case Z_ACCEPT:
    default:
      break;
    }
  z_policy_unlock(self->super.thread);
  z_proxy_leave(self);
  return TRUE;
}

/*+

  main proxy routine. called by whois_thread.

  +*/
static void
whois_main(WhoisProxy *self)
{

  z_proxy_enter(self);  
  if (!whois_init_client_stream(self))
    {
      z_proxy_leave(self);
      return;
    }

  z_proxy_log(self, WHOIS_DEBUG, 6, "fetching request");
  if (!whois_fetch_request(self))
    {
      char *errmsg = "Whois protocol error or disallowed protocol element, request denied.\r\n";
      gint bytes_written;
      
      z_stream_write(self->super.endpoints[EP_CLIENT], errmsg, strlen(errmsg), &bytes_written);
      z_proxy_leave(self);
      return;
    }
  
  z_proxy_log(self, WHOIS_DEBUG, 6, "asking policy");
  
  if (!whois_query_policy(self))
    {
      z_proxy_leave(self);
      return;
    }
  
  z_proxy_log(self, WHOIS_DEBUG, 6, "connecting server");
  
  /* this sets the server side endpoint if successful */
  if (!z_proxy_connect_server_event(&self->super, NULL, 0))
    {
      z_proxy_leave(self);
      return;
    }

  if (!whois_init_server_stream(self))
    {
      z_proxy_leave(self);
      return;
    }
    
  z_proxy_log(self, WHOIS_DEBUG, 6, "sending request");
  if (!whois_send_request(self))
    {
      z_proxy_leave(self);
      return;
    }
    
  z_proxy_log(self, WHOIS_DEBUG, 6, "copying response");
  if (!whois_copy_response(self))
    {
      z_proxy_leave(self);
      return;
    }

  z_proxy_log(self, WHOIS_DEBUG, 6, "everything is done");
  z_proxy_leave(self);
}

/*+

  Thread function. whois allocates a new thread for each connection.

  +*/
static void
whois_thread(gpointer s)
{
  WhoisProxy *self = (WhoisProxy *) s;

  z_proxy_enter(self);
  whois_config_set_defaults(self);  
  whois_register_vars(self);
  
  ZPROXY_SET_STATE(self, PS_CONFIG);
  if (!z_proxy_config_event(&self->super))
    ZPROXY_SET_FLAG(self,PF_QUIT);
  else
    {
      whois_config_init(self);
      
      ZPROXY_SET_STATE(self, PS_STARTING_UP);
      if (!z_proxy_startup_event(&self->super))
        ZPROXY_SET_FLAG(self,PF_QUIT);
    }

  if (!(ZPROXY_GET_FLAG(self,PF_QUIT)))
    {
      ZPROXY_SET_STATE(self, PS_WORKING);

      whois_main(self);
    }
  ZPROXY_SET_STATE(self, PS_SHUTTING_DOWN);
  z_proxy_shutdown_event(&self->super);
  
  z_proxy_destroy(&self->super);
  z_leave();
}

/* forward declaration */
static void whois_proxy_free(ZProxy *self);

/*+

  Whois proxy constructor. Allocates and initializes a proxy instance,
  starts proxy thread.

  +*/
static ZProxy *
whois_proxy_new(gchar *session_id, ZStream *client, ZPolicyObj *handler)
{
  WhoisProxy  *self = g_new0(WhoisProxy, 1);
  
  z_enter();
  z_proxy_init(&self->super, session_id, client, handler);
  self->super.free = whois_proxy_free;
  
  z_thread_new(session_id, whois_thread, self);
  z_leave();
  return (ZProxy *) self;
}

static void
whois_proxy_free(ZProxy *s)
{
  WhoisProxy *self = (WhoisProxy *) s;
  int i;
  
  z_proxy_enter(self);
  for (i = EP_CLIENT; i <= EP_SERVER; i++)
    {
      z_read_line_destroy(self->readline[i]);
      self->readline[i] = NULL;
    }
#if 0
  g_string_free(self->request, TRUE);
  g_string_free(self->response_header, TRUE);
  g_string_free(self->response_footer, TRUE);
#endif
  z_proxy_leave(self);
}

/*+

  Module initialization function. Registers a new proxy type.
  
  +*/
gint
zorp_module_init(void)
{
  z_registry_add("whois", ZR_SPROXY, whois_proxy_new);
  return TRUE;
}
