/***************************************************************************
 *
 * 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: pytisauth.c,v 1.6.2.3 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : bazsi
 * Auditor : 
 * Last version: 1.4
 * Notes:
 *
 ***************************************************************************/

#include <zorp/pytisauth.h>
#include <zorp/pysockaddr.h>
#include <zorp/policy.h>
#include <zorp/zpython.h>
#include <zorp/log.h>

typedef struct _ZTISSession
{
  ZTISContext *tis;
  gchar *challenge;
} ZTISSession;

static ZTISSession *
z_tis_session_new(ZSockAddr *authserv)
{
  ZTISContext *tis;
  ZTISSession *self = NULL;

  if (authserv)
    {
      tis = z_tis_new(authserv);
      if (tis)
        {
          self = g_new0(ZTISSession, 1);
          self->tis = tis;
        }
    }
  else
    {
      self = g_new0(ZTISSession, 1);
    }

  return self;
}

static void
z_tis_session_destroy(ZTISSession *self)
{
  if (self->tis)
    z_tis_destroy(self->tis);
  g_free(self->challenge);
  g_free(self);
}

typedef struct _ZorpTISAuth
{
  PyObject_HEAD
  ZSockAddr *authserv;
  gboolean extended_tis;
  ZTISSession *single;
  /* hashtable lock, might be changed to an r/w lock */
  GStaticMutex lock;
  GHashTable *pool;
} ZorpTISAuth;

extern PyTypeObject z_py_zorp_tisauth_type;

PyObject *
z_py_zorp_tisauth_new_instance(PyObject *s, PyObject *args)
{
  ZorpTISAuth *self;
  ZorpSockAddr *sa;
  int extended_tis = 1;
  
  z_enter();

  if (!PyArg_Parse(args, "(Oi)", &sa, &extended_tis))
    {
      z_leave();
      return NULL;
    }
  
  if (!z_py_zorp_sockaddr_check(sa))
    {
      PyErr_SetString(PyExc_TypeError, "ZorpSockAddr expected");
      z_leave();
      return NULL;
    }
  self = PyObject_NEW(ZorpTISAuth, &z_py_zorp_tisauth_type);
  if (!self)
    {
      z_leave();
      return NULL;
    }
  self->extended_tis = extended_tis;
  self->pool = g_hash_table_new(g_str_hash, g_str_equal);
  self->authserv = z_sockaddr_ref(sa->sa);
  self->single = NULL;
  memset(&self->lock, 0, sizeof(self->lock));
  z_leave();
  return (PyObject *) self;
}

static gint
z_py_zorp_tisauth_remove_tis_connections(gpointer key, gpointer value, gpointer user_data)
{
  z_enter();
  g_free(key);
  z_tis_session_destroy((ZTISSession *) value);
  z_leave();
  return 1;
}

void
z_py_zorp_tisauth_free(PyObject *s)
{
  ZorpTISAuth *self = (ZorpTISAuth *) s;
  
  z_enter();
  z_sockaddr_unref(self->authserv);
  if (self->pool)
    {
      g_hash_table_foreach_remove(self->pool, z_py_zorp_tisauth_remove_tis_connections, NULL);
      g_hash_table_destroy(self->pool);
    }
  if (self->extended_tis && self->single)
    {
      z_tis_session_destroy(self->single);
    }
  
  PyMem_DEL(self);
  z_leave();
}

PyObject *
z_py_zorp_tisauth_start_session(PyObject *s, PyObject *args)
{
  z_enter();

  Py_XINCREF(Py_None);
  z_leave();
  return Py_None;
}

PyObject *
z_py_zorp_tisauth_stop_session(PyObject *s, PyObject *args)
{
  ZorpTISAuth *self = (ZorpTISAuth *) s;
  gchar *sessionid, *orig_key;
  ZTISSession *tis_session;

  z_enter();
  if (!PyArg_Parse(args, "(s)", &sessionid))
    {
      z_leave();
      return FALSE;
    }
  g_static_mutex_lock(&self->lock);
  if (g_hash_table_lookup_extended(self->pool, sessionid, (gpointer *) &orig_key, (gpointer *) &tis_session))
    {
      g_static_mutex_unlock(&self->lock);
      g_hash_table_remove(self->pool, sessionid);
      g_free(orig_key);
      z_tis_session_destroy(tis_session);
    }
  else
    g_static_mutex_unlock(&self->lock);
  Py_XINCREF(Py_None);
  z_leave();
  return Py_None;
}

PyObject *
z_py_zorp_tisauth_get_methods(PyObject *s, PyObject *args)
{
  ZorpTISAuth *self = (ZorpTISAuth *) s;
  ZTISSession *tis_session;
  PyObject *methods;
  gchar *entity, *sessionid, buf[128];
  gboolean success;
  
  z_enter();
  if (!PyArg_Parse(args, "(ss)", &sessionid, &entity))
    {
      z_leave();
      return FALSE;
    }

  if (self->extended_tis)
    {
      if (!self->single)
        {
          self->single = z_tis_session_new(self->authserv);

          if (!self->single)
            {
              PyErr_SetString(PyExc_IOError, "Error connecting to TIS authserv.");
              z_leave();
              return NULL;
            }
        }

      Py_BEGIN_ALLOW_THREADS
      success = z_tis_challenge(self->single->tis, buf, sizeof(buf), entity);
      Py_END_ALLOW_THREADS

      if (!success)
        {
          PyErr_SetString(PyExc_IOError, "Error communicating with TIS authserv or protocol error.");
          z_tis_session_destroy(self->single);
          self->single = NULL;
          z_leave();
          return NULL;
        }

      tis_session = z_tis_session_new(NULL);
    }
  else
    {
    
      tis_session = z_tis_session_new(self->authserv);

      if (!tis_session)
        {
          PyErr_SetString(PyExc_IOError, "Error communicating with TIS authserv or protocol error.");
          z_leave();
          return NULL;
        }

      Py_BEGIN_ALLOW_THREADS
      success = z_tis_authenticate(tis_session->tis, buf, sizeof(buf), entity);
      Py_END_ALLOW_THREADS

      if (!success)
        {
          PyErr_SetString(PyExc_IOError, "Error communicating with TIS authserv or protocol error.");
          z_tis_session_destroy(tis_session);
          z_leave();
          return NULL;
        }
   
    }
    
  g_static_mutex_lock(&self->lock);  
  g_hash_table_insert(self->pool, g_strdup(sessionid), tis_session);
  g_static_mutex_unlock(&self->lock);
  if (buf[0])
    {
      methods = PyTuple_New(1);
      PyTuple_SetItem(methods, 0, PyString_FromString("challenge-response"));
      tis_session->challenge = g_strdup(buf);
    }
  else
    {
      methods = PyTuple_New(1);
      PyTuple_SetItem(methods, 0, PyString_FromString("password"));
    }
  z_leave();
  return methods;
}

PyObject *
z_py_zorp_tisauth_get_challenge(PyObject *s, PyObject *args)
{
  ZorpTISAuth *self = (ZorpTISAuth *) s;
  gchar *entity, *method, *sessionid;
  ZTISSession *tis_session;
  
  z_enter();
  if (!z_policy_var_parse(args, "(sss)", &sessionid, &entity, &method))
    {
      z_leave();
      return NULL;
    }
  
  g_static_mutex_lock(&self->lock);
  tis_session = g_hash_table_lookup(self->pool, sessionid);
  g_static_mutex_unlock(&self->lock);
  if (tis_session)
    {
      if (tis_session->challenge)
        {
          z_leave();
          return PyString_FromString(tis_session->challenge);
        }
      else
        {
          Py_XINCREF(Py_None);
          z_leave();
          return Py_None;
        }
    }
  PyErr_SetString(PyExc_IOError, "z_py_zorp_tisauth_get_challenge with no TISSession.");
  z_leave();
  return NULL;
}

PyObject *
z_py_zorp_tisauth_check_credentials(PyObject *s, PyObject *args)
{
  ZorpTISAuth *self = (ZorpTISAuth *) s;
  gchar *sessionid, *entity, *method, *challenge, *credential, *orig_key;
  ZTISSession *tis_session = NULL;
  ZPolicyObj *res;
  
  z_enter();
  if (!z_policy_var_parse(args, "(sssss)", &sessionid, &entity, &method, &challenge, &credential))
    {
      z_leave();
      return NULL;
    }

  g_static_mutex_lock(&self->lock);
  if (g_hash_table_lookup_extended(self->pool, sessionid, (gpointer *) &orig_key, (gpointer *) &tis_session))
    {
      gint valid;
      
      g_static_mutex_unlock(&self->lock);
      
      if (self->extended_tis)
        {
          Py_BEGIN_ALLOW_THREADS
          valid = z_tis_respchal(self->single->tis, entity, challenge, credential);
          Py_END_ALLOW_THREADS
        }
      else
        {
          Py_BEGIN_ALLOW_THREADS
          valid = z_tis_response(tis_session->tis, credential);
          Py_END_ALLOW_THREADS
        }

      if (valid)
        res = PyInt_FromLong(1);
      else
        res = PyInt_FromLong(0);

      g_hash_table_remove(self->pool, sessionid);
      g_free(orig_key);
      z_tis_session_destroy(tis_session);
    }
  else
    {
      g_static_mutex_unlock(&self->lock);
      res = PyInt_FromLong(0);
    }
    
  z_leave();
  return res;
}

static PyMethodDef py_zorp_tisauth_methods[] =
{
  { "startSession",    (PyCFunction) z_py_zorp_tisauth_start_session, METH_VARARGS },
  { "stopSession",     (PyCFunction) z_py_zorp_tisauth_stop_session, METH_VARARGS },
  { "getMethods",      (PyCFunction) z_py_zorp_tisauth_get_methods, METH_VARARGS },
  { "getChallenge",    (PyCFunction) z_py_zorp_tisauth_get_challenge, METH_VARARGS },
  { "checkCredentials",(PyCFunction) z_py_zorp_tisauth_check_credentials, METH_VARARGS },
  { NULL,              NULL }   /* sentinel*/
};

static gint
z_py_zorp_tisauth_setattr(PyObject *o, char *name, PyObject *value)
{
  return -1;
}

static PyObject *
z_py_zorp_tisauth_getattr(PyObject *o, char *name)
{
  PyObject *res;

  z_enter();  
  res = Py_FindMethod(py_zorp_tisauth_methods, o, name);
  z_leave();
  return res;
}

PyTypeObject z_py_zorp_tisauth_type =
{
  PyObject_HEAD_INIT(&PyType_Type)
  0,                                        /*ob_size*/
  "ZorpTISAuth",                            /*tp_name*/
  sizeof(ZorpTISAuth),                      /*tp_basicsize*/
  0,                                        /*tp_itemsize*/
  /* methods */
  (destructor)z_py_zorp_tisauth_free,       /*tp_dealloc*/
  (printfunc)0,                             /*tp_print*/
  (getattrfunc)z_py_zorp_tisauth_getattr,   /*tp_getattr*/
  (setattrfunc)z_py_zorp_tisauth_setattr,   /*tp_setattr*/
  (cmpfunc)0,                               /*tp_compare*/
  (reprfunc)0,                              /*tp_repr*/
  0,                                        /*tp_as_number*/
  0,                   		            /*tp_as_sequence*/
  0,                   		            /*tp_as_mapping*/
  (hashfunc)0,         		            /*tp_hash*/
  (ternaryfunc)0,      		            /*tp_call*/
  (reprfunc)0,                              /*tp_str*/

  /* Space for future expansion */
  0L,0L,0L,0L,
  "ZorpTISAuth class", /* Documentation string */
};

PyMethodDef z_py_zorp_tisauth_funcs[] =
{
  { "ZorpTISAuthorization",  z_py_zorp_tisauth_new_instance, METH_VARARGS },
  { NULL,       NULL }   /* sentinel*/
};

void
z_py_zorp_tisauth_init(void)
{
  z_enter();
  Py_InitModule("Zorp.Zorp", z_py_zorp_tisauth_funcs);
  z_leave();
}
