/*****************************************************************************
*
* Copyright (c) 2003, 2004 Zope Foundation and Contributors.
* All Rights Reserved.
*
* This software is subject to the provisions of the Zope Public License,
* Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
* WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
* FOR A PARTICULAR PURPOSE.
*
******************************************************************************
Security Proxy Implementation
*/

#include <Python.h>
#include "zope/proxy/proxy.h"

static PyObject *__class__str = 0, *__name__str = 0, *__module__str = 0;

#define PyInt_FromLong PyLong_FromLong

#define IS_STRING PyUnicode_Check

#define MAKE_STRING(name) PyBytes_AS_STRING( \
        PyUnicode_AsUTF8String(name))

#define FROM_STRING PyUnicode_FromString

#define FROM_STRING_FORMAT PyUnicode_FromFormat

#define INTERN PyUnicode_InternFromString

#define MOD_ERROR_VAL NULL

#define MOD_SUCCESS_VAL(val) val

#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)

#define MOD_DEF(ob, name, doc, methods) \
        static struct PyModuleDef moduledef = { \
          PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
        ob = PyModule_Create(&moduledef);

#define DECLARE_STRING(N) static PyObject *str_##N

DECLARE_STRING(__3pow__);
DECLARE_STRING(__bool__);
DECLARE_STRING(__call__);
DECLARE_STRING(check);
DECLARE_STRING(check_getattr);
DECLARE_STRING(check_setattr);
DECLARE_STRING(__cmp__);
DECLARE_STRING(__contains__);
DECLARE_STRING(__delitem__);
DECLARE_STRING(__getitem__);
DECLARE_STRING(__hash__);
DECLARE_STRING(__iter__);
DECLARE_STRING(__len__);
DECLARE_STRING(__next__);
DECLARE_STRING(op_abs);
DECLARE_STRING(op_add);
DECLARE_STRING(op_and);
DECLARE_STRING(op_divmod);
DECLARE_STRING(op_float);
DECLARE_STRING(op_floordiv);
DECLARE_STRING(op_iadd);
DECLARE_STRING(op_iand);
DECLARE_STRING(op_ifloordiv);
DECLARE_STRING(op_ilshift);
DECLARE_STRING(op_imod);
DECLARE_STRING(op_imul);
DECLARE_STRING(op_int);
DECLARE_STRING(op_invert);
DECLARE_STRING(op_ior);
DECLARE_STRING(op_ipow);
DECLARE_STRING(op_irshift);
DECLARE_STRING(op_isub);
DECLARE_STRING(op_itruediv);
DECLARE_STRING(op_ixor);
DECLARE_STRING(op_lshift);
DECLARE_STRING(op_mod);
DECLARE_STRING(op_mul);
DECLARE_STRING(op_neg);
DECLARE_STRING(op_or);
DECLARE_STRING(op_pos);
DECLARE_STRING(op_radd);
DECLARE_STRING(op_rand);
DECLARE_STRING(op_rdivmod);
DECLARE_STRING(op_rfloordiv);
DECLARE_STRING(op_rlshift);
DECLARE_STRING(op_rmod);
DECLARE_STRING(op_rmul);
DECLARE_STRING(op_ror);
DECLARE_STRING(op_rrshift);
DECLARE_STRING(op_rshift);
DECLARE_STRING(op_rsub);
DECLARE_STRING(op_rtruediv);
DECLARE_STRING(op_rxor);
DECLARE_STRING(op_sub);
DECLARE_STRING(op_truediv);
DECLARE_STRING(op_xor);
DECLARE_STRING(__pow__);
DECLARE_STRING(proxy);
DECLARE_STRING(__repr__);
DECLARE_STRING(__rpow__);
DECLARE_STRING(__setitem__);
DECLARE_STRING(__str__);

typedef struct {
  ProxyObject proxy;
  PyObject *proxy_checker;
} SecurityProxy;

#define CLEAR(O) if (O) {PyObject *t = O; O = 0; Py_DECREF(t); }

#undef Proxy_Check
#define Proxy_Check(proxy) \
    PyObject_TypeCheck(proxy, &SecurityProxyType)

static PyTypeObject SecurityProxyType;

/*
 * Machinery to call the checker.
 */

static int
check(SecurityProxy *self, PyObject *meth, PyObject *name)
{
  PyObject *r;

  /* If the checker has __setitem__, we call it's slot rather than
     calling check or check_getattr. Why? Because calling operator slots
     is much faster than calling methods and security checks are done so
     often that speed matters.  So we have this hack of using
     almost-arbitrary operations to represent methods that we call
     alot.  */
  if (self->proxy_checker->ob_type->tp_as_mapping != NULL
      && self->proxy_checker->ob_type->tp_as_mapping->mp_ass_subscript != NULL
      && meth != str_check_setattr)
    return self->proxy_checker->ob_type->tp_as_mapping->
      mp_ass_subscript(self->proxy_checker, self->proxy.proxy_object, name);

  r = PyObject_CallMethodObjArgs(self->proxy_checker, meth,
                                 self->proxy.proxy_object, name,
                                 NULL);
  if (r == NULL)
    return -1;

  Py_DECREF(r);
  return 0;
}

/* If the checker has __getitem__, we call it's slot rather than
   calling proxy. Why? Because calling operator slots
   is much faster than calling methods and security checks are done so
   often that speed matters.  So we have this hack of using
   almost-arbitrary operations to represent methods that we call
   alot.  */
#define PROXY_RESULT(self, result) \
if (result != NULL) { \
  PyObject *tmp; \
  if (self->proxy_checker->ob_type->tp_as_mapping != NULL \
      && self->proxy_checker->ob_type->tp_as_mapping->mp_subscript != NULL) \
    tmp = self->proxy_checker->ob_type->tp_as_mapping-> \
      mp_subscript(self->proxy_checker, result); \
  else \
    tmp = PyObject_CallMethodObjArgs(self->proxy_checker, str_proxy, \
                                     result, NULL); \
  Py_DECREF(result); \
  result = tmp; \
}

typedef PyObject *(*function1)(PyObject *);

static PyObject *
check1(SecurityProxy *self, PyObject *opname, function1 operation)
{
  PyObject *result = NULL;

  if (check(self, str_check, opname) >= 0) {
    result = operation(self->proxy.proxy_object);
    PROXY_RESULT(self, result);
  }
  return result;
}

static PyObject *
check2(PyObject *self, PyObject *other,
       PyObject *opname, PyObject *ropname, binaryfunc operation)
{
  PyObject *result = NULL;

  if (Proxy_Check(self))
    {
      if (check((SecurityProxy*)self, str_check, opname) >= 0)
        {
          result = operation(((SecurityProxy*)self)->proxy.proxy_object,
                             other);
          PROXY_RESULT(((SecurityProxy*)self), result);
        }
    }
  else if (Proxy_Check(other))
    {
      if (check((SecurityProxy*)other, str_check, ropname) >= 0)
        {
          result = operation(self,
                             ((SecurityProxy*)other)->proxy.proxy_object);

          PROXY_RESULT(((SecurityProxy*)other), result);
        }
    }
  else
    {
      Py_INCREF(Py_NotImplemented);
      return Py_NotImplemented;
    }

  return result;
}

static PyObject *
check2i(SecurityProxy *self, PyObject *other,
    PyObject *opname, binaryfunc operation)
{
  PyObject *result = NULL;

  if (check(self, str_check, opname) >= 0)
    {
      result = operation(self->proxy.proxy_object, other);
      if (result == self->proxy.proxy_object)
        {
          /* If the operation was really carried out inplace,
             don't create a new proxy, but use the old one. */
          Py_DECREF(result);
          Py_INCREF((PyObject *)self);
          result = (PyObject *)self;
        }
      else
        PROXY_RESULT(self, result);
    }
  return result;
}

#define UNOP(NAME, CALL) \
    static PyObject *proxy_##NAME(PyObject *self) \
    { return check1((SecurityProxy *)self, str_op_##NAME, CALL); }

#define BINOP(NAME, CALL) \
    static PyObject *proxy_##NAME(PyObject *self, PyObject *other) \
    { return check2(self, other, str_op_##NAME, str_op_r##NAME, CALL); }

#define INPLACE(NAME, CALL) \
    static PyObject *proxy_i##NAME(PyObject *self, PyObject *other) \
    { return check2i((SecurityProxy *)self, other, str_op_i##NAME, CALL); }


/*
 * Slot methods.
 */

static PyObject *
proxy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  static char *kwlist[] = {"object", "checker", 0};
  SecurityProxy *self;
  PyObject *object;
  PyObject *checker;

  if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                   "OO:_Proxy.__new__", kwlist,
                                   &object, &checker))
    return NULL;

  if (checker == Py_None)
    {
      PyErr_SetString(PyExc_ValueError, "None passed as proxy checker");
      return NULL;
    }

  self = (SecurityProxy *)type->tp_alloc(type, 0);
  if (self == NULL)
    return NULL;
  Py_INCREF(object);
  Py_INCREF(checker);
  self->proxy.proxy_object = object;
  self->proxy_checker = checker;
  return (PyObject *)self;
}

/* This is needed to avoid calling the base class tp_init, which we
   don't need. */
static int
proxy_init(PyObject *self, PyObject *args, PyObject *kw)
{
  return 0;
}

static int
proxy_clear(SecurityProxy *self)
{
  CLEAR(self->proxy_checker);
  SecurityProxyType.tp_base->tp_clear((PyObject*)self);
  return 0;
}

static void
proxy_dealloc(SecurityProxy *self)
{
  PyObject_GC_UnTrack((PyObject*)self);
  proxy_clear(self);
  SecurityProxyType.tp_base->tp_dealloc((PyObject*)self);
}

static int
proxy_traverse(SecurityProxy *self, visitproc visit, void *arg)
{
  Py_VISIT(self->proxy.proxy_object);
  Py_VISIT(self->proxy_checker);
  return 0;
}

static PyObject *
proxy_richcompare(SecurityProxy* self, PyObject* other, int op)
{
  PyObject *result = NULL;

  result = PyObject_RichCompare(self->proxy.proxy_object, other, op);
  if (result == Py_True || result == Py_False)
    return result;
  PROXY_RESULT(self, result);
  return result;
}

static PyObject *
proxy_iter(SecurityProxy *self)
{
  PyObject *result = NULL;

  if (check(self, str_check, str___iter__) >= 0)
    {
      result = PyObject_GetIter(self->proxy.proxy_object);
      PROXY_RESULT(self, result);
    }
  return result;
}

static PyObject *
proxy_iternext(SecurityProxy *self)
{
  PyObject *result = NULL;

  if (check(self, str_check_getattr, str___next__) >= 0)
    {
      result = PyIter_Next(self->proxy.proxy_object);
      PROXY_RESULT(self, result);
    }
  return result;
}

static PyObject *
proxy_getattro(SecurityProxy *self, PyObject *name)
{
  PyObject *result = NULL;

  if (check(self, str_check_getattr, name) >= 0)
    {
      result = PyObject_GetAttr(self->proxy.proxy_object, name);
      PROXY_RESULT(self, result);
    }
  return result;
}

static int
proxy_setattro(SecurityProxy *self, PyObject *name, PyObject *value)
{
  if (check(self, str_check_setattr, name) >= 0)
    return PyObject_SetAttr(self->proxy.proxy_object, name, value);
  return -1;
}

static PyObject *
default_repr(PyObject *object)
{
  PyObject *klass, *name = 0, *module = 0, *result = 0;
  char *sname, *smodule;

  klass = PyObject_GetAttr(object, __class__str);
  if (klass == NULL)
    return NULL;

  name  = PyObject_GetAttr(klass, __name__str);
  if (name == NULL)
    goto err;
  sname = MAKE_STRING(name);
  if (sname == NULL)
    goto err;

  module = PyObject_GetAttr(klass, __module__str);
  if (module != NULL) {
    smodule = MAKE_STRING(module);
    if (smodule == NULL)
      goto err;
    result = FROM_STRING_FORMAT("<security proxied %s.%s instance at %p>",
                                 smodule, sname, object);
  }
  else {
    PyErr_Clear();
    result = FROM_STRING_FORMAT("<security proxied %s instance at %p>",
                                 sname, object);
  }

 err:
  Py_DECREF(klass);
  Py_XDECREF(name);
  Py_XDECREF(module);

  return result;
}

static PyObject *
proxy_str(SecurityProxy *self)
{
  PyObject *result = NULL;

  if (check(self, str_check, str___str__) >= 0)
    {
      result = PyObject_Str(self->proxy.proxy_object);
    }
  else
    {
      PyErr_Clear();
      result = default_repr(self->proxy.proxy_object);
    }
  return result;
}

static PyObject *
proxy_repr(SecurityProxy *self)
{
  PyObject *result = NULL;

  if (check(self, str_check, str___repr__) >= 0) {
    result = PyObject_Repr(self->proxy.proxy_object);
  }
  else {
    PyErr_Clear();
    result = default_repr(self->proxy.proxy_object);
  }
  return result;
}

static long
proxy_hash(SecurityProxy *self)
{
  return PyObject_Hash(self->proxy.proxy_object);
}

static PyObject *
proxy_call(SecurityProxy *self, PyObject *args, PyObject *kwds)
{
  PyObject *result = NULL;

  if (check(self, str_check, str___call__) >= 0)
    {
      result = PyObject_Call(self->proxy.proxy_object, args, kwds);
      PROXY_RESULT(self, result);
    }
  return result;
}

/*
 * Number methods.
 */

#define NUMBER_METHOD(M) \
static PyObject * \
call_##M(PyObject *self) \
{ \
  PyNumberMethods *nb = self->ob_type->tp_as_number; \
  if (nb == NULL || nb->nb_##M == NULL) { \
    PyErr_SetString(PyExc_TypeError, \
                    "object can't be converted to " #M); \
    return NULL; \
  } \
  return nb->nb_##M(self); \
}

NUMBER_METHOD(int)
NUMBER_METHOD(float)

static PyObject *
call_ipow(PyObject *self, PyObject *other)
{
  /* PyNumber_InPlacePower has three args.  How silly. :-) */
  return PyNumber_InPlacePower(self, other, Py_None);
}

BINOP(add, PyNumber_Add)
BINOP(sub, PyNumber_Subtract)
BINOP(mul, PyNumber_Multiply)
BINOP(mod, PyNumber_Remainder)
BINOP(divmod, PyNumber_Divmod)

static PyObject *
proxy_pow(PyObject *self, PyObject *other, PyObject *modulus)
{
  PyObject *result = NULL;

  if (Proxy_Check(self))
    {
      if (check((SecurityProxy*)self, str_check, str___pow__) >= 0)
        {
          result = PyNumber_Power(((SecurityProxy*)self)->proxy.proxy_object,
                                  other, modulus);
          PROXY_RESULT(((SecurityProxy*)self), result);
        }
    }
  else if (Proxy_Check(other))
    {
      if (check((SecurityProxy*)other, str_check, str___rpow__) >= 0)
        {
          result = PyNumber_Power(self,
                                  ((SecurityProxy*)other)->proxy.proxy_object,
                                  modulus);
          PROXY_RESULT(((SecurityProxy*)other), result);
        }
    }
  else if (modulus != NULL && Proxy_Check(modulus))
    {
      if (check((SecurityProxy*)modulus, str_check, str___3pow__) >= 0)
        {
          result = PyNumber_Power(self, other,
                               ((SecurityProxy*)modulus)->proxy.proxy_object);
          PROXY_RESULT(((SecurityProxy*)modulus), result);
        }
    }
  else {
    Py_INCREF(Py_NotImplemented);
    return Py_NotImplemented;
  }
  return result;
}

BINOP(lshift, PyNumber_Lshift)
BINOP(rshift, PyNumber_Rshift)
BINOP(and, PyNumber_And)
BINOP(xor, PyNumber_Xor)
BINOP(or, PyNumber_Or)

UNOP(neg, PyNumber_Negative)
UNOP(pos, PyNumber_Positive)
UNOP(abs, PyNumber_Absolute)

static int
proxy_bool(PyObject *self)
{
  return PyObject_IsTrue(((SecurityProxy*)self)->proxy.proxy_object);
}

UNOP(invert, PyNumber_Invert)
UNOP(int, call_int)
UNOP(float, call_float)

INPLACE(add, PyNumber_InPlaceAdd)
INPLACE(sub, PyNumber_InPlaceSubtract)
INPLACE(mul, PyNumber_InPlaceMultiply)
INPLACE(mod, PyNumber_InPlaceRemainder)
INPLACE(pow, call_ipow)
INPLACE(lshift, PyNumber_InPlaceLshift)
INPLACE(rshift, PyNumber_InPlaceRshift)
INPLACE(and, PyNumber_InPlaceAnd)
INPLACE(xor, PyNumber_InPlaceXor)
INPLACE(or, PyNumber_InPlaceOr)

BINOP(floordiv, PyNumber_FloorDivide)
BINOP(truediv, PyNumber_TrueDivide)
INPLACE(floordiv, PyNumber_InPlaceFloorDivide)
INPLACE(truediv, PyNumber_InPlaceTrueDivide)

/*
 * Sequence methods.
 */

static Py_ssize_t
proxy_length(SecurityProxy *self)
{
  if (check(self, str_check, str___len__) >= 0)
    return PyObject_Length(self->proxy.proxy_object);
  return -1;
}

static PyObject *
proxy_length_hint(SecurityProxy *self)
{
  PyObject *result = NULL;
  result = PyObject_CallMethod(self->proxy.proxy_object, "__length_hint__", NULL);
  if (result == NULL) {
      if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
            PyErr_Clear();
            result = Py_NotImplemented;
            Py_INCREF(result);
      }
  }
  return result;
}

/* sq_item and sq_ass_item may be called by PySequece_{Get,Set}Item(). */
static PyObject *proxy_getitem(SecurityProxy *, PyObject *);
static int proxy_setitem(SecurityProxy *, PyObject *, PyObject *);

static PyObject *
proxy_igetitem(SecurityProxy *self, Py_ssize_t i)
{
  PyObject *key = PyInt_FromLong(i);
  PyObject *res = NULL;

  if (key != NULL) {
    res = proxy_getitem(self, key);
    Py_DECREF(key);
  }
  return res;
}


static int
proxy_isetitem(SecurityProxy *self, Py_ssize_t i, PyObject *value)
{
  PyObject *key = PyInt_FromLong(i);
  int res = -1;

  if (key != NULL) {
    res = proxy_setitem(self, key, value);
    Py_DECREF(key);
  }
  return res;
}


static int
proxy_contains(SecurityProxy *self, PyObject *value)
{
  if (check(self, str_check, str___contains__) >= 0)
    return PySequence_Contains(self->proxy.proxy_object, value);
  return -1;
}

/*
 * Mapping methods.
 */

static PyObject *
proxy_getitem(SecurityProxy *self, PyObject *key)
{
  PyObject *result = NULL;

  if (check(self, str_check, str___getitem__) >= 0)
    {
      result = PyObject_GetItem(self->proxy.proxy_object, key);
      PROXY_RESULT(self, result);
    }
  return result;
}

static int
proxy_setitem(SecurityProxy *self, PyObject *key, PyObject *value)
{
  if (value == NULL) {
    if (check(self, str_check, str___delitem__) >= 0)
      return PyObject_DelItem(self->proxy.proxy_object, key);
  }
  else {
    if (check(self, str_check, str___setitem__) >= 0)
      return PyObject_SetItem(self->proxy.proxy_object, key, value);
  }
  return -1;
}

/*
 * Normal methods.
 */

static PyNumberMethods
proxy_as_number = {
    proxy_add,                      /* nb_add */
    proxy_sub,                      /* nb_subtract */
    proxy_mul,                      /* nb_multiply */
    proxy_mod,                      /* nb_remainder */
    proxy_divmod,                   /* nb_divmod */
    proxy_pow,                      /* nb_power */
    proxy_neg,                      /* nb_negative */
    proxy_pos,                      /* nb_positive */
    proxy_abs,                      /* nb_absolute */
    proxy_bool,                     /* nb_bool */
    proxy_invert,                   /* nb_invert */
    proxy_lshift,                   /* nb_lshift */
    proxy_rshift,                   /* nb_rshift */
    proxy_and,                      /* nb_and */
    proxy_xor,                      /* nb_xor */
    proxy_or,                       /* nb_or */
    proxy_int,                      /* nb_int */
    0,                              /* nb_reserved, formerly nb_long */
    proxy_float,                    /* nb_float */

    /* Added in release 2.0 */
    /* These require the Py_TPFLAGS_HAVE_INPLACEOPS flag */
    proxy_iadd,                     /* nb_inplace_add */
    proxy_isub,                     /* nb_inplace_subtract */
    proxy_imul,                     /* nb_inplace_multiply */
    proxy_imod,                     /* nb_inplace_remainder */
    (ternaryfunc)proxy_ipow,        /* nb_inplace_power */
    proxy_ilshift,                  /* nb_inplace_lshift */
    proxy_irshift,                  /* nb_inplace_rshift */
    proxy_iand,                     /* nb_inplace_and */
    proxy_ixor,                     /* nb_inplace_xor */
    proxy_ior,                      /* nb_inplace_or */

    /* Added in release 2.2 */
    /* These require the Py_TPFLAGS_HAVE_CLASS flag */
    proxy_floordiv,                 /* nb_floor_divide */
    proxy_truediv,                  /* nb_true_divide */
    proxy_ifloordiv,                /* nb_inplace_floor_divide */
    proxy_itruediv,                 /* nb_inplace_true_divide */
};

static PySequenceMethods
proxy_as_sequence = {
  (lenfunc)proxy_length,                    /* sq_length */
  0,                                        /* sq_concat */
  0,                                        /* sq_repeat */
  (ssizeargfunc)proxy_igetitem,             /* sq_item */
  0,                                        /* sq_slice, unused in PY3 */
  (ssizeobjargproc)proxy_isetitem,          /* sq_ass_item */
  0,                                        /* sq_ass_slice, unused in PY3 */
  (objobjproc)proxy_contains,               /* sq_contains */
};

static PyMappingMethods
proxy_as_mapping = {
  (lenfunc)proxy_length,                /* mp_length */
  (binaryfunc)proxy_getitem,                /* mp_subscript */
  (objobjargproc)proxy_setitem,                /* mp_ass_subscript */
};

static char proxy_doc[] = "\
Security proxy class.  Constructor: _Proxy(object, checker)\n\
where 'object' is an arbitrary object, and 'checker' is an object\n\
whose signature is described by the IChecker interface.\n\
A checker should have the following methods:\n\
  check(object, operation) # operation is e.g. '__add__' or '__hash__'\n\
  check_getattr(object, name)\n\
  check_setattr(object, name)\n\
  proxy(object)\n\
The check methods should raise an exception if the operation is\n\
disallowed.  The proxy method should return a proxy for the object\n\
if one is needed, otherwise the object itself.\n\
";

static PyMethodDef proxy_methods[] = {
    {"__length_hint__", (PyCFunction)proxy_length_hint, METH_NOARGS,
     "Guess the length of the object"},
    {NULL,              NULL}           /* sentinel */
};


static PyTypeObject SecurityProxyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "zope.security._proxy._Proxy",
    sizeof(SecurityProxy),
    0,
    (destructor)proxy_dealloc,                /* tp_dealloc */
    0,                                        /* tp_print */
    0,                                        /* tp_getattr */
    0,                                        /* tp_setattr */
    0,                                        /* tp_reserved, was tp_compare */
    (reprfunc)proxy_repr,                     /* tp_repr */
    &proxy_as_number,                         /* tp_as_number */
    &proxy_as_sequence,                       /* tp_as_sequence */
    &proxy_as_mapping,                        /* tp_as_mapping */
    (hashfunc)proxy_hash,                     /* tp_hash */
    (ternaryfunc)proxy_call,                  /* tp_call */
    (reprfunc)proxy_str,                      /* tp_str */
    (getattrofunc)proxy_getattro,             /* tp_getattro */
    (setattrofunc)proxy_setattro,             /* tp_setattro */
    0,                                        /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
    Py_TPFLAGS_HAVE_GC |
    Py_TPFLAGS_BASETYPE,                      /* tp_flags */
    proxy_doc,                                /* tp_doc */
    (traverseproc)proxy_traverse,             /* tp_traverse */
    0,                                        /* tp_clear */
    (richcmpfunc)proxy_richcompare,           /* tp_richcompare */
    0,                                        /* tp_weaklistoffset */
    (getiterfunc)proxy_iter,                  /* tp_iter */
    (iternextfunc)proxy_iternext,             /* tp_iternext */
    proxy_methods,                            /* tp_methods */
    0,                                        /* tp_members */
    0,                                        /* tp_getset */
    0,                                        /* tp_base */
    0,                                        /* tp_dict */
    0,                                        /* tp_descr_get */
    0,                                        /* tp_descr_set */
    0,                                        /* tp_dictoffset */
    proxy_init,                               /* tp_init */
    0, /*PyType_GenericAlloc,*/               /* tp_alloc */
    proxy_new,                                /* tp_new */
    0, /*_PyObject_GC_Del,*/                  /* tp_free */
};

static PyObject *
module_getChecker(PyObject *self, PyObject *arg)
{
  PyObject *result;

  if (!Proxy_Check(arg)) {
    PyErr_SetString(PyExc_TypeError,
                    "getChecker argument must be a _Proxy");
    return NULL;
  }
  result = ((SecurityProxy*)arg)->proxy_checker;
  Py_INCREF(result);
  return result;
}

static PyObject *
module_getObject(PyObject *self, PyObject *arg)
{
  PyObject *result;

  if (!Proxy_Check(arg))
    result = arg;
  else
    result = ((SecurityProxy*)arg)->proxy.proxy_object;

  Py_INCREF(result);
  return result;
}

static char
module___doc__[] = "Security proxy implementation.";

static PyMethodDef
module_functions[] = {
  {"getChecker", module_getChecker, METH_O, "get checker from proxy"},
  {"getObject", module_getObject, METH_O,
   "Get the proxied object\n\nReturn the original object if not proxied."},
  {NULL}
};

MOD_INIT(_proxy)
{
  PyObject *m;

  MOD_DEF(m, "_proxy", module___doc__, module_functions)

  if (m == NULL)
    return MOD_ERROR_VAL;

  if (Proxy_Import() < 0)
    return MOD_ERROR_VAL;

#define INIT_STRING(S) \
if((str_##S = INTERN(#S)) == NULL) return MOD_ERROR_VAL
#define INIT_STRING_OP(S) \
if((str_op_##S = INTERN("__" #S "__")) == NULL) return MOD_ERROR_VAL

  INIT_STRING(__3pow__);
  INIT_STRING(__bool__);
  INIT_STRING(__call__);
  INIT_STRING(check);
  INIT_STRING(check_getattr);
  INIT_STRING(check_setattr);
  INIT_STRING(__cmp__);
  INIT_STRING(__contains__);
  INIT_STRING(__delitem__);
  INIT_STRING(__getitem__);
  INIT_STRING(__hash__);
  INIT_STRING(__iter__);
  INIT_STRING(__len__);
  INIT_STRING(__next__);
  INIT_STRING_OP(abs);
  INIT_STRING_OP(add);
  INIT_STRING_OP(and);
  INIT_STRING_OP(divmod);
  INIT_STRING_OP(float);
  INIT_STRING_OP(floordiv);
  INIT_STRING_OP(iadd);
  INIT_STRING_OP(iand);
  INIT_STRING_OP(ifloordiv);
  INIT_STRING_OP(ilshift);
  INIT_STRING_OP(imod);
  INIT_STRING_OP(imul);
  INIT_STRING_OP(int);
  INIT_STRING_OP(invert);
  INIT_STRING_OP(ior);
  INIT_STRING_OP(ipow);
  INIT_STRING_OP(irshift);
  INIT_STRING_OP(isub);
  INIT_STRING_OP(itruediv);
  INIT_STRING_OP(ixor);
  INIT_STRING_OP(lshift);
  INIT_STRING_OP(mod);
  INIT_STRING_OP(mul);
  INIT_STRING_OP(neg);
  INIT_STRING_OP(or);
  INIT_STRING_OP(pos);
  INIT_STRING_OP(radd);
  INIT_STRING_OP(rand);
  INIT_STRING_OP(rdivmod);
  INIT_STRING_OP(rfloordiv);
  INIT_STRING_OP(rlshift);
  INIT_STRING_OP(rmod);
  INIT_STRING_OP(rmul);
  INIT_STRING_OP(ror);
  INIT_STRING_OP(rrshift);
  INIT_STRING_OP(rshift);
  INIT_STRING_OP(rsub);
  INIT_STRING_OP(rtruediv);
  INIT_STRING_OP(rxor);
  INIT_STRING_OP(sub);
  INIT_STRING_OP(truediv);
  INIT_STRING_OP(xor);
  INIT_STRING(__pow__);
  INIT_STRING(proxy);
  INIT_STRING(__repr__);
  INIT_STRING(__rpow__);
  INIT_STRING(__setitem__);
  INIT_STRING(__str__);


  __class__str = FROM_STRING("__class__");
  if (! __class__str)
     return MOD_ERROR_VAL;

  __name__str = FROM_STRING("__name__");
  if (! __name__str)
    return MOD_ERROR_VAL;

  __module__str = FROM_STRING("__module__");
  if (! __module__str) return MOD_ERROR_VAL;

  SecurityProxyType.tp_alloc = PyType_GenericAlloc;
  SecurityProxyType.tp_free = PyObject_GC_Del;
  SecurityProxyType.tp_base = &ProxyType;
  if (PyType_Ready(&SecurityProxyType) < 0)
    return MOD_ERROR_VAL;

  Py_INCREF(&SecurityProxyType);
  PyModule_AddObject(m, "_Proxy", (PyObject *)&SecurityProxyType);

  return MOD_SUCCESS_VAL(m);
}
