/***************************************************************************
 *
 * 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: proxy.c,v 1.37.2.5.2.3 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.23
 * Notes:
 *
 ***************************************************************************/

#include <zorp/proxy.h>
#include <zorp/policy.h>
#include <zorp/pyvars.h>
#include <zorp/pystream.h>
#include <zorp/log.h>
#include <zorp/io.h>

#include <stdarg.h>
#include <sys/socket.h>
#include <unistd.h>

#define CORE_DEBUG "core.debug"

/* a two-way connection between streams */

/* z_proxy_var_new() should be here, but is implemented in pyvars.c for now */

/*+

  Initialize a given proxy instance.

  Parameters:
    self       proxy instance to initialize
    session_id session_id
    client     client side Stream
    handler    policy object to send events to

  Returns:
    none

  +*/
void
z_proxy_init(ZProxy *self, 
             gchar *session_id, 
	     ZStream *client, 
	     ZPolicyObj *handler)
{
  z_enter();
  
  if (client)
    {
      self->endpoints[EP_CLIENT] = client;
      z_stream_ref(client);
    }
  
  strncpy(self->session_id, session_id, MAX_SESSION_ID);
  self->handler = handler;
  z_policy_var_ref(handler);
  self->setattr = z_pyvars_setattr;
  self->getattr = z_pyvars_getattr;
  self->vars = z_pyvars_new();
  self->thread = PyThreadState_New(z_python_global_state->interp);
  
  z_leave();
}

/*+

  Called to free up a proxy instance. Frees up associated
  resources, closes streams, and calls the free virtual method.

  Parameters:
    self         proxy instance

  Returns:
    none

  +*/
void
z_proxy_destroy(ZProxy *self)
{
  int i;
  ZPolicyObj *handler;
  ZPolicyThread *thread;

  z_enter();

  z_proxy_destroy_event(self);
  
  for (i = EP_CLIENT; i <= EP_SERVER; i++)
    {
      if (self->endpoints[i])
        {
          z_stream_shutdown(self->endpoints[i], SHUT_RDWR);
          z_stream_close(self->endpoints[i]);
          z_stream_unref(self->endpoints[i]);
          self->endpoints[i] = NULL;
        }
    }
    
  z_policy_lock(self->thread);
  thread = self->thread;
  self->thread = NULL;
  
  z_pyvars_destroy(self->vars);
  self->vars = NULL;

  handler = self->handler;
  self->handler = NULL;
  z_policy_var_unref(handler);  

  z_policy_unlock(thread);

  /* NOTE: this is Python specific */  
  z_python_lock();
  PyThreadState_Clear(thread);
  PyThreadState_Delete(thread);
  z_python_unlock();

  z_leave();
}

void
z_proxy_free(ZProxy *self)
{
  z_enter();

  if (self->free)
    (*self->free)(self);
    
  g_free(self);
  z_leave();
}

void
z_proxy_quit(ZProxy *self)
{
  self->flags |= PF_QUIT;
}

/*+

  Send a config event to the associated policy object.

  Parameters:
    self      proxy instance

  Returns:
    TRUE if config is called, and ended succesfully

  Remarks:
    Locks the policy engine.

  +*/
gboolean
z_proxy_config_event(ZProxy *self)
{
  ZPolicyObj *res;
  gboolean called;
  
  z_policy_lock(self->thread);

  /*LOG
    This debug message is written when Zorp is about to call a user
    supplied __config__() event.
   */
  z_proxy_log(self, CORE_DEBUG, 7, "calling __config__() event;");
  res = z_policy_call(self->handler, "__config__", NULL, &called);
  if (res == NULL && called)
    {
      z_policy_unlock(self->thread);
      return FALSE;
    }
  z_policy_var_unref(res);

  /*LOG
    This debug message is written when Zorp is about to call a user
    supplied config() event.
   */
  z_proxy_log(self, CORE_DEBUG, 7, "calling config() event;");
  res = z_policy_call(self->handler, "config", NULL, &called);
  if (res == NULL && called)
    {
      z_policy_unlock(self->thread);
      return FALSE;
    }
  z_policy_var_unref(res);
  z_policy_unlock(self->thread);
  return TRUE;
}

/*+

  Send a startup event to the associated policy object.

  Parameters:
    self      proxy instance

  Returns:
    TRUE if startup event be callable.

  Remarks:
    Locks the policy engine.

  +*/
gboolean
z_proxy_startup_event(ZProxy *self)
{
  ZPolicyObj *res;
  gboolean called;

  z_policy_lock(self->thread);
  /*LOG
    This debug message is written when Zorp is about to call a user
    supplied __startup__() event.
   */
  z_proxy_log(self, CORE_DEBUG, 7, "calling __startup__() event;");
  res = z_policy_call(self->handler, "__startup__", NULL, &called);
  if (res == NULL && called)
    {
      z_policy_unlock(self->thread);
      return FALSE;
    }
  z_policy_var_unref(res);

  /*LOG
    This debug message is written when Zorp is about to call a user
    supplied startUp() event.
   */
  z_proxy_log(self, CORE_DEBUG, 7, "calling startUp() event;");
  res = z_policy_call(self->handler, "startUp", NULL, &called);
  if (res == NULL && called)
    {
      z_policy_unlock(self->thread);
      return FALSE;
    }
  z_policy_var_unref(res);
  z_policy_unlock(self->thread);
  return TRUE;
}

/*+

  Send a shutdown event to the associated policy object.

  Parameters:
    self      proxy instance

  Returns:
    none

  Remarks:
    Locks the policy engine.

  +*/
void 
z_proxy_shutdown_event(ZProxy *self)
{
  ZPolicyObj *res;
  gboolean called;

  z_policy_lock(self->thread);

  /*LOG
    This debug message is written when Zorp is about to call a user
    supplied __shutdown__() event.
   */
  z_proxy_log(self, CORE_DEBUG, 7, "calling __shutdown__() event;");
  res = z_policy_call(self->handler, "__shutdown__", NULL, &called);
  z_policy_var_unref(res);

  /*LOG
    This debug message is written when Zorp is about to call a user
    supplied shutDown() event.
   */
  z_proxy_log(self, CORE_DEBUG, 7, "calling shutDown() event;");
  res = z_policy_call(self->handler, "shutDown", NULL, &called);
  z_policy_var_unref(res);
  z_policy_unlock(self->thread);
}

/*+

  Send a __destroy__ event to the associated policy object.

  Parameters:
    self      proxy instance

  Returns:
    none

  Remarks:
    Locks the policy engine.

  +*/
void 
z_proxy_destroy_event(ZProxy *self)
{
  ZPolicyObj *res;
  gboolean called;

  /*LOG
    This debug message is written when Zorp is about to call a user
    supplied __destroy__() event.
   */
  z_proxy_log(self, CORE_DEBUG, 7, "calling __destroy__() event;");
  z_policy_lock(self->thread);
  res = z_policy_call(self->handler, "__destroy__", NULL, &called);
  z_policy_var_unref(res);
  z_policy_unlock(self->thread);
}

/*+

  Send a connectServer event to the associated policy object.

  Parameters:
    self      proxy instance
    host      host to connect to, used as a hint by the 
              policy layer but may as well be ignored
    port      port in host to connect to

  Returns:
    TRUE if the server-side connection is established, otherwise the
    connection should be closed.

  Remarks:
    Locks the policy engine.

  +*/
gint
z_proxy_connect_server_event(ZProxy *self, char *host, gint port)
{
  ZPolicyObj *res, *args;
  gint rc;
  gboolean called;
  
  z_proxy_enter(self);

  z_policy_lock(self->thread);
  self->endpoints[EP_SERVER] = NULL;
  if (host)
    {
      args = z_policy_var_build("(si)", host, port);
      res = z_policy_call(self->handler, "setServerAddress", args, &called);
      if (!res)
        {
          z_policy_unlock(self->thread);
          z_proxy_leave(self);
          return FALSE;
        }
    }

  res = z_policy_call(self->handler, "connectServer", NULL, &called);

  if (res && z_py_zorp_stream_check(res))
    {
      self->endpoints[EP_SERVER] = ((ZorpStream *) res)->stream;
      z_stream_ref(self->endpoints[EP_SERVER]);
      rc = 1;
    }
  else
    {
      rc = 0;
    }
  z_policy_var_unref(res);
  z_policy_unlock(self->thread);
  z_proxy_leave(self);
  return rc;
}

gint
z_proxy_user_authenticated(ZProxy *self, gchar *entity)
{
  ZPolicyObj *res;
  gboolean called;
  gint rc = 1;
  
  z_proxy_enter(self);
  z_policy_lock(self->thread);

  res = z_policy_call(self->handler, "userAuthenticated", z_policy_var_build("(s)", entity), &called);
  if (!res)
    rc = 0;
  z_policy_var_unref(res);
  z_policy_unlock(self->thread);
  z_proxy_leave(self);
  return rc;
}

ZStackedProxy *
z_proxy_stack_proxy(ZProxy *self, ZPolicyObj *proxy_class)
{
  int downpair[2], uppair[2];
  ZPolicyObj *res;
  
  if (socketpair(AF_UNIX, SOCK_STREAM, 0, downpair) == -1)
    {
      /*LOG
        This message indicates that stacking a child proxy failed, because
        creating an AF_UNIX domain socketpair failed on the client side.
       */
      z_proxy_log(self, CORE_ERROR, 3, "Error creating client socketpair for stacked proxy; error='%m'");
      z_policy_var_unref(proxy_class);
      return NULL;
    }
  else if (socketpair(AF_UNIX, SOCK_STREAM, 0, uppair) == -1)
    {
      close(downpair[0]);
      close(downpair[1]);
      /*LOG
        This message indicates that stacking a child proxy failed, because
        creating an AF_UNIX domain socketpair failed on the server side.
       */
      z_proxy_log(self, CORE_ERROR, 3, "Error creating server socketpair for stacked proxy; error='%m'");
      z_policy_var_unref(proxy_class);
      return NULL;
    }
  /*LOG
    This debug message indicates that Zorp is about to stack a proxy class
    with the given fds as communication channels.
   */
  z_proxy_log(self, CORE_DEBUG, 6, "Stacking subproxy; client='%d:%d', server='%d:%d'", downpair[0], downpair[1], uppair[0], uppair[1]);
  res = z_policy_call(self->handler, "stackProxy", z_policy_var_build("(iiO)", downpair[1], uppair[1], proxy_class), NULL);
  
  if (!res)
    {
      close(downpair[0]);
      close(downpair[1]);
      close(uppair[0]);
      close(uppair[1]);
      z_policy_var_unref(res);
      return NULL;
    }
  z_policy_var_unref(res);
  return z_stacked_proxy_new(downpair[0], uppair[0], self);
}

/* stacked proxy */

ZStackedProxy *
z_stacked_proxy_new(gint client_fd, gint server_fd, ZProxy *proxy)
{
  ZStackedProxy *self = g_new0(ZStackedProxy, 1);
  gchar buf[MAX_SESSION_ID];
  
  z_fd_set_nonblock(client_fd, TRUE);
  z_fd_set_nonblock(server_fd, TRUE);

  g_snprintf(buf, sizeof(buf), "%s: client downstream", proxy->session_id);
  self->downstreams[EP_CLIENT] = z_stream_new(client_fd, buf);
  
  g_snprintf(buf, sizeof(buf), "%s: server downstream", proxy->session_id);
  self->downstreams[EP_SERVER] = z_stream_new(server_fd, buf);
  
  return self;
}

void
z_stacked_proxy_destroy(ZStackedProxy *self)
{
  int i;

  for (i = 0; i < EP_MAX; i++)
    {
      if (self->downstreams[i])
        {
          z_stream_shutdown(self->downstreams[i], SHUT_RDWR);
          z_stream_close(self->downstreams[i]);
          z_stream_unref(self->downstreams[i]);
        }
    }
  g_free(self);
}
