/***************************************************************************
 *
 * 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: pylisten.c,v 1.27.2.4.2.3 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.18
 * Notes:
 *   - comments
 *   - process identifiers
 *
 ***************************************************************************/

#include <zorp/pylisten.h>
#include <zorp/io.h>
#include <zorp/log.h>
#include <zorp/policy.h>

#include <zorp/pysockaddr.h>

/*
 * struct ZorpListen
 *
 * Author:  Bazsi, 2000/03/23
 * Purpose: this class encapsulates a listener
 * Notes:
 * Audit: kisza, 2000/07/31
 *
 */

struct _ZorpListen 
{
  PyObject_HEAD
  ZIOListen *listen;
  PyObject *handler;
};

static PyObject *z_py_zorp_listen_new_instance(PyObject *o, PyObject *args);
static void z_py_zorp_listen_free(ZorpListen *self);

#if 0
static void z_py_zorp_listen_destroy(ZorpListen *self);
#endif

static PyObject *z_py_zorp_listen_getattr(PyObject *o, char *name);

static PyObject *z_py_zorp_listen_start_method(ZorpListen *self, PyObject *args);
static PyObject *z_py_zorp_listen_destroy_method(ZorpListen *self, PyObject *args);

PyMethodDef z_py_zorp_listen_funcs[] =
{
  { "Listen",  z_py_zorp_listen_new_instance, METH_VARARGS },
  { NULL,      NULL }   /* sentinel*/
};

static PyMethodDef py_zorp_listen_methods[] =
{
  { "start",       (PyCFunction) z_py_zorp_listen_start_method, 0 },
  { "destroy",     (PyCFunction) z_py_zorp_listen_destroy_method, 0 },
  { NULL,          NULL }   /* sentinel*/
};

PyTypeObject z_py_zorp_listen_type = 
{
  PyObject_HEAD_INIT(&z_py_zorp_listen_type)
  0,
  "ZorpListen",
  sizeof(ZorpListen),
  0,
  (destructor) z_py_zorp_listen_free,
  0,
  (getattrfunc) z_py_zorp_listen_getattr,
  0,
  0,
  0, /*(reprfunc) z_py_zorp_listen_repr,*/
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  "ZorpListen class for Zorp",
};

/*
   Called by the main thread, so it locks using the global python state
 */
static void 
z_py_zorp_listen_accept(int fd, 
                        ZSockAddr *client, 
                        gboolean last_connection, 
                        gpointer user_data)
{
  char buf[128];
  ZorpListen *self = (ZorpListen *) user_data;
  PyObject *res, *addr;

  z_policy_lock(z_python_global_state);
  if (last_connection)
    {
      ZIOListen *listen = self->listen;
      
      z_trace(NULL, "self=%p, last connection TRUE", self);
      /* drop reference */
      self->listen = NULL;
      z_io_listen_unref(listen);
    }
  if (fd == -1)
    {
      if (last_connection)
        {
          Py_XDECREF(self);
        }
      z_policy_unlock(z_python_global_state);
      return;
    }
  /*LOG
    This message indicates an incoming connection from the indicated
    remote peer.
   */
  z_log(NULL, CORE_DEBUG, 6, "Incoming connection; remote='%s', fd='%d'", 
	    z_sockaddr_format(client, buf, sizeof(buf)), fd);
  
  if (!(addr = z_py_zorp_sockaddr_new(client)))
    {
      /*LOG
        This message indicates that exporting a ZSockAddr object to
        Python was not successful. It is usually caused by out-of-memory
        condition, or as an internal error.
       */
      z_log(NULL, CORE_ERROR, 1, "Error converting sockaddr to python;");
      close(fd);
    }
  else
    {
      res = PyEval_CallFunction(self->handler, "(Oi)", addr, fd);
      Py_XDECREF(addr);

      if (!res || PyErr_Occurred())
        {
          PyErr_Print();
          /* close(fd); */
        }
      else if (PyInt_Check(res) && PyInt_AsLong(res) != Z_ACCEPT)
        {
          /*LOG
            This message indicates that the decision layer denied the
            given connection.
           */
          z_log(NULL, CORE_POLICY, 1, "Connection denied by policy; remote='%s'", buf);
          /* close(fd); */
        }
    }
  z_sockaddr_unref(client);
  z_policy_unlock(z_python_global_state);
}

static PyObject *
z_py_zorp_listen_new_instance(PyObject *o, PyObject *args)
{
  ZorpListen *self;
  ZorpSockAddr *addr;
  PyObject *handler;
  gboolean accept_one;
  gint backlog;
  char buf[128];

  /* called by python, so interpreter is locked */
  if (!PyArg_ParseTuple(args, "OOii", &addr, &handler, &accept_one, &backlog))
    return NULL;
  if (!z_py_zorp_sockaddr_check(addr))
    {
      PyErr_SetString(PyExc_TypeError, "addr parameter must be a ZorpSockAddr");
      return NULL;
    }
  if (!PyCallable_Check(handler))
    {
      PyErr_SetString(PyExc_TypeError, "Handler parameter must be callable");
      return NULL;
    }

  /*LOG
    This message indicates that a Listener on the given local address is
    started.
   */
  z_log(NULL, CORE_DEBUG, 7, "Listening on address; local=%s", z_sockaddr_format(addr->sa, buf, sizeof(buf)));

  self = PyObject_NEW(ZorpListen, &z_py_zorp_listen_type);
  if (!self)
    return NULL;
    
  Py_XINCREF(self);
  self->handler = handler;
  Py_XINCREF(handler);
  self->listen = z_io_listen_new(addr->sa, accept_one, backlog, z_py_zorp_listen_accept, self);
  if (!self->listen)
    {
      Py_XDECREF(self);
      Py_XDECREF(self);
      PyErr_SetString(PyExc_IOError, "Error binding to interface");
      return NULL;
    }

  return (PyObject *) self;
}

static void
z_py_zorp_listen_free(ZorpListen *self)
{
  if (self->handler)
    {
      Py_XDECREF(self->handler);
      self->handler = NULL;
    }
  if (self->listen)
    {
      z_io_listen_unref(self->listen);
      self->listen = NULL;
    }
  PyMem_DEL(self);
}

#if 0
static void
z_py_zorp_listen_destroy(ZorpListen *self)
{
  z_policy_lock(z_python_global_state);
  Py_XDECREF(self);
  z_policy_unlock(z_python_global_state);
}
#endif

static PyObject *
z_py_zorp_listen_getattr(PyObject *o, char *name)
{
  ZorpListen *self = (ZorpListen *) o;
  if (strcmp(name, "local") == 0)
    {
      if (self->listen)
        return z_py_zorp_sockaddr_new(self->listen->local);
      else
        {
          Py_XINCREF(Py_None);
          return Py_None;
        }
    }
  else
    return Py_FindMethod(py_zorp_listen_methods, o, name);
}

static PyObject *
z_py_zorp_listen_start_method(ZorpListen *self, PyObject *args)
{
  if (self->listen)
    z_io_listen_start(self->listen);
  else
    z_trace(NULL, "py_listen.start, self=%p, self->listen = NULL", self);
  Py_XINCREF(Py_None);
  return Py_None;
}

static PyObject *
z_py_zorp_listen_destroy_method(ZorpListen *self, PyObject *args)
{
  if (self->listen)
    {
      ZIOListen *listen = self->listen;
      
      self->listen = NULL;
      z_io_listen_unref(listen);
    }
  Py_XDECREF(self->handler);
  self->handler = NULL;
  Py_XDECREF(self);
  Py_XINCREF(Py_None);
  return Py_None;
}

void
z_py_zorp_listen_init(void)
{
  Py_InitModule("Zorp.Zorp", z_py_zorp_listen_funcs);
}
