Skip to content
Snippets Groups Projects
miltermodule.c 53.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* Copyright (C) 2001  James Niemira (niemira@colltech.com, urmane@urmane.org)
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Portions Copyright (C) 2001,2002,2003,2004,2005,2006,2007
     *   Stuart Gathman (stuart@bmsi.com)
     *
     * 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.,
     * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    
     *
     * milterContext object and thread interface contributed by
     * 	Stuart D. Gathman <stuart@bmsi.com>
     */
    
    /* This is a Python extension to use Sendmail's libmilter functionality.  
       It is built using distutils.  To install it:
    
    # python setup.py install 
    
       For additional options:
    
    $ python setup.py help
      
       You may need to add additional libraries to setup.py.  For instance,
       Solaris2.6 requires 
    
         libraries=["milter","smutil","resolv"]
    
     * $Log$
    
     * Revision 1.28  2011/06/08 23:13:48  customdesigned
     * Generate special exception when callback return not int.
     *
    
     * Revision 1.27  2009/07/28 21:45:54  customdesigned
     * Add getversion() to return runtime version.
     *
    
     * Revision 1.26  2009/07/28 21:08:20  customdesigned
     * Increment del count.
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 1.25  2009/07/28 20:58:55  customdesigned
     * getdiag method
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 1.24  2009/06/09 01:54:44  customdesigned
     * Forgot to initialize optional parameter.
     *
    
     * Revision 1.23  2009/05/29 20:44:58  customdesigned
     * Typo SMFIP_NO constants.
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 1.22  2009/05/29 19:53:36  customdesigned
     * Typo SMFIS_ALL_OPTS
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 1.21  2009/05/29 19:49:40  customdesigned
     * Typo calling helo instead of negotiate.
     *
    
     * Revision 1.20  2009/05/29 18:25:59  customdesigned
     * Null terminate keyword list.
     *
    
     * Revision 1.19  2009/05/28 18:36:42  customdesigned
     * Support new callbacks, including negotiate
     *
    
     * Revision 1.18  2009/05/21 21:53:05  customdesigned
     * First cut at support unknown, data, negotiate callbacks.
     *
    
     * Revision 1.17  2009/02/06 04:28:08  customdesigned
     * Oops!  Missing options argument pointer for addrcpt.
     *
    
     * Revision 1.16  2008/12/16 04:21:05  customdesigned
     * Fedora release
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 1.15  2008/12/13 20:29:56  customdesigned
     * Split off milter applications.
     *
    
     * Revision 1.14  2008/12/04 19:43:00  customdesigned
     * Doc updates.
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 1.13  2008/11/23 03:06:47  customdesigned
     * Milter support for chgfrom.
     *
    
     * Revision 1.12  2008/11/21 20:42:52  customdesigned
     * Support smfi_chgfrom and smfi_addrcpt_par.
     *
    
     * Revision 1.11  2007/09/25 02:26:29  customdesigned
     * Update license.
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 1.10  2006/02/12 02:00:42  customdesigned
     * Resolve FIXME for wrap_close.
     *
    
     * Revision 1.9  2005/12/23 21:46:36  customdesigned
     * Compile on sendmail-8.12 (ifdef SMFIR_INSHEADER)
     *
    
     * Revision 1.8  2005/10/20 23:23:36  customdesigned
     * Include smfi_progress is SMFIR_PROGRESS defined
     *
    
     * Revision 1.7  2005/10/20 23:04:46  customdesigned
     * Add optional idx for position of added header.
     *
    
     * Revision 1.6  2005/07/15 22:18:17  customdesigned
     * Support callback exception policy
     *
    
     * Revision 1.5  2005/06/24 04:20:07  customdesigned
     * Report context allocation error.
     *
    
     * Revision 1.4  2005/06/24 04:12:43  customdesigned
     * Remove unused name argument to generic wrappers.
     *
    
     * Revision 1.3  2005/06/24 03:57:35  customdesigned
     * Handle close called before connect.
     *
    
     * Revision 1.2  2005/06/02 04:18:55  customdesigned
     * Update copyright notices after reading article on /.
     *
    
     * Revision 1.1.1.2  2005/05/31 18:09:06  customdesigned
     * Release 0.7.1
     *
    
    Stuart Gathman's avatar
    Stuart Gathman committed
     * Revision 2.31  2004/08/23 02:24:36  stuart
     * Support setbacklog
     *
     * Revision 2.30  2004/08/21 20:29:53  stuart
     * Support option of 11 lines max for mlreply.
     *
     * Revision 2.29  2004/08/21 04:14:29  stuart
     * mlreply support
     *
     * Revision 2.28  2004/08/21 02:45:21  stuart
     * Don't leak int constants if module unloaded.
     *
    
     * Revision 2.27  2004/04/06 03:19:59  stuart
     * Release 0.6.8
     *
     * Revision 2.26  2004/03/04 21:43:06  stuart
     * Fix memory leak by removing unused dynamic template buffer,
     * thanks again to Alexander Kourakos.
     *
     * Revision 2.25  2004/03/01 19:45:03  stuart
     * Release 0.6.5
     *
     * Revision 2.24  2004/03/01 18:56:50  stuart
     * Support progress reporting.
     *
     * Revision 2.23  2004/03/01 18:36:09  stuart
     * Plug memory leak.  Thanks to Alexander Kourakos.
     *
     * Revision 2.22  2003/11/02 03:01:46  stuart
     * Adjust SMTP error codes after careful reading of standard.
     *
     * Revision 2.21  2003/06/24 19:57:04  stuart
     * Allow removing a python milter callback by setting to None.
     *
     * Revision 2.20  2003/02/13 17:08:57  stuart
     * IPV6 support
     *
     * Revision 2.19  2003/02/13 16:58:29  stuart
     * Support passing None to setreply and chgheader.
     *
     * Revision 2.18  2002/12/11 16:44:06  stuart
     * Support QUARANTINE if supported by libmilter.
     *
     * Revision 2.17  2002/04/18 20:20:35  stuart
     * Fix for NULL hostaddr in connect callback from Jason Erickson.
     *
     * Revision 2.16  2001/09/26 13:29:09  stuart
     * sa_len not supported by linux.
     *
     * Revision 2.15  2001/09/25 17:28:40  stuart
     * Copyrights, documentation, release 0.3.1
     *
     * Revision 2.14  2001/09/25 00:36:57  stuart
     * Pass hostaddr to python code in format used by standard socket module.
     *
     * Revision 2.13  2001/09/24 23:44:55  stuart
     * Return old callback from setcallback functions.
     *
     * Revision 2.12  2001/09/24 20:02:30  stuart
     * Remove redundant setpriv
     *
     * Revision 2.11  2001/09/23 22:26:35  stuart
     * Update docs.  Streamline Milter.py
     * update testbms.py to reflect actual sendmail behaviour with multiple
     * messages per connection.
     *
     * Revision 2.10  2001/09/22 15:33:42  stuart
     * More doc comment updates.
     *
     * Revision 2.9  2001/09/22 14:52:27  stuart
     * Actually return retval in _generic_return.
     * Go over doc comments.
     *
     * Revision 2.8  2001/09/22 01:59:32  stuart
     * Prevent reentrant call of milter_main, which libmilter doesn't support.
     *
     * Revision 2.7  2001/09/22 01:47:37  stuart
     * Forgot to set milter interp.
     *
     * Revision 2.6  2001/09/22 01:23:53  stuart
     * Added proper threading after research in python docs.
     *
     * Revision 2.5  2001/09/21 20:08:51  stuart
     * Release 0.2.3
     *
     * Revision 2.4  2001/09/20 16:18:16  stuart
     * libmilter checks in_eom state, so we don't have to.
     *
     * Revision 2.3  2001/09/19 06:02:33  stuart
     * Make more stuff static.
     *
     * Revision 2.1  2001/09/19 04:24:13  stuart
     * Use extension type to track context in python.
     *
     * Revision 1.4  2001/09/18 18:48:28  stuart
     * clear private data reference in _clear_context
     *
     * Revision 1.3  2001/09/15  04:19:37  stuart
     * nasty off by 1 mem overwrite bugs in wrap_env
     * generic_set_callback
     *
     * Revision 1.2  2001/09/15  03:15:39  stuart
     * several bugs fixed, works smoothly
     *
     */
    
    
    Stuart Gathman's avatar
    Stuart Gathman committed
    #ifndef MAX_ML_REPLY
    #define MAX_ML_REPLY 32
    #endif
    #if MAX_ML_REPLY != 1 && MAX_ML_REPLY != 32 && MAX_ML_REPLY != 11
    #error MAX_ML_REPLY must be 1 or 11 or 32
    #endif
    #define _FFR_MULTILINE (MAX_ML_REPLY > 1)
    
    
    //#include <pthread.h>	// shouldn't be needed - use Python API
    #include <Python.h>		// Python C API
    #include <libmilter/mfapi.h>	// libmilter API
    #include <netinet/in.h>		// socket API
    
    Stuart Gathman's avatar
    Stuart Gathman committed
    
    
    /* See if we have IPv4 and/or IPv6 support in this OS and in
     * libmilter.  We need to make several macro tests because some OS's
     * may define some if IPv6 is only partially supported, and we may
     * have a sendmail without IPv4 (compiled for IPv6-only).
     */
    #ifdef SMFIA_INET
    #ifdef AF_INET
    #define HAVE_IPV4_SUPPORT /* use this for #ifdef's later on */
    #endif
    #endif
    
    #ifdef SMFIA_INET6
    #ifdef AF_INET6
    #ifdef IN6ADDR_ANY_INIT
    #ifdef INET6_ADDRSTRLEN
    #define HAVE_IPV6_SUPPORT /* use this for #ifdef's later on */
    /* Now see if it supports the RFC-2553 socket's API spec.  Early
     * IPv6 "prototype" implementations existed before the RFC was
     * published.  Unfortunately I know of now good way to do this
     * other than with OS-specific tests.
     */
    #ifdef linux
    #define HAVE_IPV6_RFC2553
    #include <arpa/inet.h>
    #endif
    #ifdef __HPUX
    /* only HP-UX 11.1 or greater supports IPv6 */
    #define HAVE_IPV6_RFC2553
    #endif
    
    #endif
    #endif
    #endif
    #endif
    
    /* Yes, these are static.  If you need multiple different callbacks, */
    
    /* it's cleaner to use multiple filters, or convert to OO method calls. */
    
    static PyObject *connect_callback = NULL;
    static PyObject *helo_callback    = NULL;
    static PyObject *envfrom_callback = NULL;
    static PyObject *envrcpt_callback = NULL;
    static PyObject *header_callback  = NULL;
    static PyObject *eoh_callback     = NULL;
    static PyObject *body_callback    = NULL;
    static PyObject *eom_callback     = NULL;
    static PyObject *abort_callback   = NULL;
    static PyObject *close_callback   = NULL;
    
    #ifdef SMFIS_ALL_OPTS
    static PyObject *unknown_callback = NULL;
    static PyObject *data_callback    = NULL;
    static PyObject *negotiate_callback = NULL;
    #endif
    static struct MilterCallback {
      PyObject **cbp;
      const char *name;
    } callback_names[] = {
          { &connect_callback,"connect" },
          { &helo_callback,"helo" },
          { &envfrom_callback,"envfrom" },
          { &envrcpt_callback,"envrcpt" },
          { &header_callback,"header" },
          { &eoh_callback,"eoh" },
          { &body_callback,"body" },
          { &eom_callback,"eom" },
          { &abort_callback,"abort" },
          { &close_callback,"close" },
    #ifdef SMFIS_ALL_OPTS
          { &unknown_callback,"unknown" },
          { &data_callback,"data" },
          { &negotiate_callback,"negotiate" },
    #endif
          { NULL, NULL }
        };
    
    
    staticforward struct smfiDesc description; /* forward declaration */
    
    static PyObject *MilterError;
    /* The interpreter instance that called milter.main */
    static PyInterpreterState *interp;
    
    Stuart Gathman's avatar
    Stuart Gathman committed
    typedef struct {
      unsigned int contextNew;
      unsigned int contextDel;
    } milter_Diag;
    
    static milter_Diag diag;
    
    
    staticforward PyTypeObject milter_ContextType;
    
    typedef struct {
      PyObject_HEAD
      SMFICTX *ctx;		/* libmilter thread state */
      PyObject *priv;	/* user python object */
      PyThreadState *t;	/* python thread state */
    } milter_ContextObject;
    
    
    Stuart Gathman's avatar
    Stuart Gathman committed
    /* Return a borrowed reference to the python Context.  Called by callbacks
       invoked by libmilter.  Create a new Context if needed.  The new
       Python Context is owned by the SMFICTX. The python interpreter is locked on
       successful return, otherwise not. */
    
    static milter_ContextObject *
    _get_context(SMFICTX *ctx) {
      milter_ContextObject *self = smfi_getpriv(ctx);
      if (self) {
        /* Can't pass on exception since we are called from libmilter */
        if (self->ctx != ctx) return NULL;
        PyEval_AcquireThread(self->t);
      }
      else {
        PyThreadState *t = PyThreadState_New(interp);
        if (t == NULL) return NULL;
        PyEval_AcquireThread(t);	/* lock interp */
        self = PyObject_New(milter_ContextObject,&milter_ContextType);
        if (!self) {
    
          /* Report and clear exception since we are called from libmilter */
          if (PyErr_Occurred()) {
    	PyErr_Print();
    	PyErr_Clear();
          }
    
          PyThreadState_Clear(t);
          PyEval_ReleaseThread(t);
          PyThreadState_Delete(t);
          return NULL;
        }
    
    Stuart Gathman's avatar
    Stuart Gathman committed
        ++diag.contextNew;
    
        self->t = t;
        self->ctx = ctx;
        Py_INCREF(Py_None);
        self->priv = Py_None;	/* User Python object */
        smfi_setpriv(ctx, self);
      }
      return self;
    }
    
    
    Stuart Gathman's avatar
    Stuart Gathman committed
    /* Find the SMFICTX from a Python Context.  Called by context methods invoked
       from python.  The interpreter must be locked. */ 
    
    static SMFICTX *
    _find_context(PyObject *c) {
      SMFICTX *ctx = NULL;
      if (c->ob_type == &milter_ContextType) {
        milter_ContextObject *self = (milter_ContextObject *)c;
        ctx = self->ctx;
    
        if (ctx != NULL && smfi_getpriv(ctx) != self)
    
          ctx = NULL;
      }
      if (ctx == NULL)
        PyErr_SetString(MilterError, "bad context");
      return ctx;
    }
    
    static void
    milter_Context_dealloc(PyObject *s) {
      milter_ContextObject *self = (milter_ContextObject *)s;
      SMFICTX *ctx = self->ctx;
      if (ctx) {
        /* Should never happen.  If libmilter closes SMFICTX first, then
          ctx will be 0.  Otherwise, SMFICTX will still hold a reference
          to the ContextObject.  But if it does, make sure SMFICTX can't
          reach us anymore. */
        smfi_setpriv(ctx,0);
      }
      Py_DECREF(self->priv);
      PyObject_DEL(self);
    
    Stuart Gathman's avatar
    Stuart Gathman committed
      ++diag.contextDel;
    
    }
    
    /* Throw an exception if an smfi call failed, otherwise return PyNone. */
    static PyObject *
    _generic_return(int val, char *errstr) {
      if (val == MI_SUCCESS) {
        Py_INCREF(Py_None);
        return Py_None;
      } else {
        PyErr_SetString(MilterError, errstr);
        return NULL;
      }
    }
    
    static PyObject *
    _thread_return(PyThreadState *t,int val,char *errstr) {
      PyEval_RestoreThread(t);	/* lock interpreter again */
      return _generic_return(val,errstr);
    }
    
    static char milter_set_flags__doc__[] =
    "set_flags(int) -> None\n\
    Set flags for filter capabilities; OR of one or more of:\n\
    ADDHDRS - filter may add headers\n\
    CHGBODY - filter may replace body\n\
    
    CHGFROM - filter may replace body\n\
    
    ADDRCPT - filter may add recipients\n\
    DELRCPT - filter may delete recipients\n\
    CHGHDRS - filter may change/delete headers";
    
    static PyObject *
    milter_set_flags(PyObject *self, PyObject *args) {
    
      if (!PyArg_ParseTuple(args, "i:set_flags", &description.xxfi_flags))
        return NULL;
    
      Py_INCREF(Py_None);
      return Py_None;
    }
    
    static PyObject *
    generic_set_callback(PyObject *args,char *t,PyObject **cb) {
      PyObject *callback;
      PyObject *oldval;
    
      if (!PyArg_ParseTuple(args, t, &callback)) return NULL;
      if (callback == Py_None)
        callback = 0;
      else {
        if (!PyCallable_Check(callback)) {
    
          PyErr_SetString(PyExc_TypeError, "callback parameter must be callable");
    
            return NULL;
        }
        Py_INCREF(callback);
      }
      oldval = *cb;
      *cb = callback;
      if (oldval)
        return oldval;
      Py_INCREF(Py_None);
      return Py_None;
    }
    
    static char milter_set_connect_callback__doc__[] =
    "set_connect_callback(Function) -> None\n\
    Sets the Python function invoked when a connection is made to sendmail.\n\
    Function takes args (ctx, hostname, integer, hostaddr) -> int\n\
    ctx -> milterContext for connection, also on remaining callbacks\n\
    hostname -> String - the connecting remote hostname\n\
    integer -> int - the protocol family, one of socket.AF_* values\n\
    hostaddr -> ? - the connecting host address in format used by socket:\n\
          for unix -> pathname - like '/tmp/sockets/s24823'\n\
          for inet -> (ipaddress, port) - like ('10.1.2.3',3701)\n\
          for inet6 -> (ip6address, port, flowlabel, scope) - like ('fec0:0:0:2::4e', 3701, 0, 5)\n\
    \n\
    The return value on this and remaining callbacks should be one of:\n\
    CONTINUE - continue processing\n\
    REJECT - sendmail refuses to accept any more data for message\n\
    ACCEPT - sendmail accepts the message\n\
    DISCARD - sendmail accepts the message and discards it\n\
    TEMPFAIL - milter problem, sendmail will try again later\n\
    \n\
    A python exception encountered in a callback will return TEMPFAIL.";
    
    static PyObject *
    milter_set_connect_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args,
        "O:set_connect_callback", &connect_callback);
    }
    
    static char milter_set_helo_callback__doc__[] =
    "set_helo_callback(Function) -> None\n\
    Sets the Python function invoked upon SMTP HELO.\n\
    Function takes args (ctx, hostname) -> int\n\
    hostname -> String - the name given with the helo command.";
    
    static PyObject *
    milter_set_helo_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_helo_callback", &helo_callback);
    }
    
    static char milter_set_envfrom_callback__doc__[] =
    "set_envfrom_callback(Function) -> None\n\
    Sets the Python function invoked on envelope from.\n\
    Function takes args (ctx, from, *str) -> int\n\
    from -> sender\n\
    str -> Tuple of additional parameters defined by ESMTP.";
    
    static PyObject *
    milter_set_envfrom_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_envfrom_callback",
      	&envfrom_callback);
    }
    
    static char milter_set_envrcpt_callback__doc__[] =
    "set_envrcpt_callback(Function) -> None\n\
    Sets the Python function invoked on each envelope recipient.\n\
    Function takes args (ctx, rcpt, *str) -> int\n\
    tcpt -> string - recipient\n\
    str -> Tuple of additional parameters defined by ESMTP.";
    
    static PyObject *
    milter_set_envrcpt_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_envrcpt_callback",
      	&envrcpt_callback);
    }
    
    static char milter_set_header_callback__doc__[] =
    "set_header_callback(Function) -> None\n\
    Sets the Python function invoked on each message header.\n\
    Function takes args (ctx, field, value) ->int\n\
    field -> String - the header\n\
    value -> String - the header's value";
    
    static PyObject *
    milter_set_header_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_header_callback",
      	&header_callback);
    }
    
    static char milter_set_eoh_callback__doc__[] =
    "set_eoh_callback(Function) -> None\n\
    Sets the Python function invoked at end of header.\n\
    Function takes args (ctx) -> int";
    
    static PyObject *
    milter_set_eoh_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_eoh_callback", &eoh_callback);
    }
    
    static char milter_set_body_callback__doc__[] =
    "set_body_callback(Function) -> None\n\
    Sets the Python function invoked for each body chunk. There may\n\
    be multiple body chunks passed to the filter. End-of-lines are\n\
    represented as received from SMTP (normally Carriage-Return/Line-Feed).\n\
    Function takes args (ctx, chunk) -> int\n\
    chunk -> String - body data";
    
    static PyObject *
    milter_set_body_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_body_callback", &body_callback);
    }
    
    static char milter_set_eom_callback__doc__[] =
    "set_eom_callback(Function) -> None\n\
    Sets the Python function invoked at end of message.\n\
    This routine is the only place where special operations\n\
    such as modifying the message header, body, or\n\
    envelope can be used.\n\
    Function takes args (ctx) -> int";
    
    static PyObject *
    milter_set_eom_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_eom_callback", &eom_callback);
    }
    
    static char milter_set_abort_callback__doc__[] =
    "set_abort_callback(Function) -> None\n\
    Sets the Python function invoked if message is aborted\n\
    outside of the control of the filter, for example,\n\
    if the SMTP sender issues an RSET command. If the \n\
    abort callback is called, the eom callback will not be\n\
    called and vice versa.\n\
    Function takes args (ctx) -> int";
    
    static PyObject *
    milter_set_abort_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_abort_callback", &abort_callback);
    }
    
    static char milter_set_close_callback__doc__[] =
    "set_close_callback(Function) -> None\n\
    Sets the Python function invoked at end of the connection.  This\n\
    is called on close even if the previous mail transaction was aborted.\n\
    Function takes args (ctx) -> int";
    
    static PyObject *
    milter_set_close_callback(PyObject *self, PyObject *args) {
      return generic_set_callback(args, "O:set_close_callback", &close_callback);
    }
    
    
    static int exception_policy = SMFIS_TEMPFAIL;
    
    static char milter_set_exception_policy__doc__[] =
    "set_exception_policy(i) -> None\n\
    Sets the policy for untrapped Python exceptions during a callback.\n\
    Must be one of TEMPFAIL,REJECT,CONTINUE";
    
    static PyObject *
    milter_set_exception_policy(PyObject *self, PyObject *args) {
      int i;
      if (!PyArg_ParseTuple(args, "i:set_exception_policy", &i))
        return NULL;
      switch (i) {
      case SMFIS_REJECT: case SMFIS_TEMPFAIL: case SMFIS_CONTINUE:
        exception_policy = i;
        Py_INCREF(Py_None);
        return Py_None;
      }
      PyErr_SetString(MilterError,"invalid exception policy");
      return NULL;
    }
    
    
    static void
    _release_thread(PyThreadState *t) {
      if (t != NULL)
        PyEval_ReleaseThread(t);
    }
    
    
    /** Report and clear any python exception before returning to libmilter. 
      The interpreter is locked when we are called, and we unlock it.  */
    static int _report_exception(milter_ContextObject *self) {
      if (PyErr_Occurred()) {
        PyErr_Print();
        PyErr_Clear();	/* must clear since not returning to python */
    
        _release_thread(self->t);
    
        switch (exception_policy) {
          case SMFIS_REJECT:
    	smfi_setreply(self->ctx, "554", "5.3.0", "Filter failure");
    	return SMFIS_REJECT;
          case SMFIS_TEMPFAIL:
    	smfi_setreply(self->ctx, "451", "4.3.0", "Filter failure");
    	return SMFIS_TEMPFAIL;
        }
        return SMFIS_CONTINUE;
    
      _release_thread(self->t);
    
      return SMFIS_CONTINUE;
    }
    
    /* Return to libmilter.  The ctx must have been initialized or
      checked by a successfull call to _get_context(), thereby locking
      the interpreter. */
    static int
    _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
      PyObject *result;
      int retval;
    
      if (arglist == NULL) return _report_exception(self);
      result = PyEval_CallObject(cb, arglist);
      Py_DECREF(arglist);
      if (result == NULL) return _report_exception(self);
    
      if (!PyInt_Check(result)) {
    
        const struct MilterCallback *p;
        const char *cbname = "milter";
        char buf[40];
    
        for (p = callback_names; p->cbp; ++p) {
          if (cb == *p->cbp) {
            cbname = p->name;
    	break;
          }
        }
        sprintf(buf,"The %s callback must return int",cbname);
        PyErr_SetString(MilterError,buf);
    
        return _report_exception(self);
      }
      retval = PyInt_AS_LONG(result);
    
      Py_DECREF(result);
    
      _release_thread(self->t);
    
      return retval;
    }
    
    /* Create a string object representing an IP address.
       This is always a string of the form 'dd.dd.dd.dd' (with variable
       size numbers). Copied from standard socket module. */
    
    static PyObject *
    makeipaddr(struct sockaddr_in *addr) {
    	long x = ntohl(addr->sin_addr.s_addr);
    	char buf[100];
    	sprintf(buf, "%d.%d.%d.%d",
    		(int) (x>>24) & 0xff, (int) (x>>16) & 0xff,
    		(int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff);
    	return PyString_FromString(buf);
    }
    
    #ifdef HAVE_IPV6_SUPPORT
    static PyObject *
    makeip6addr(struct sockaddr_in6 *addr) {
    	char buf[100]; /* must be at least INET6_ADDRSTRLEN + 1 */
    	const char *s = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof buf);
    	if (s) return PyString_FromString(s);
    	return PyString_FromString("inet6:unknown");
    }
    #endif
    
    /* These are wrapper functions to call the Python callbacks for each event */
    static int
    milter_wrap_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) {
      PyObject *arglist;
      milter_ContextObject *c;
      if (connect_callback == NULL) return SMFIS_CONTINUE;
      c = _get_context(ctx);
      if (!c) return SMFIS_TEMPFAIL;
      if (hostaddr != NULL) {
        switch (hostaddr->sa_family) {
        case AF_INET:
          { struct sockaddr_in *sa = (struct sockaddr_in *)hostaddr;
            PyObject *ipaddr_obj = makeipaddr(sa);
            arglist = Py_BuildValue("(Osh(Oi))", c, hostname, hostaddr->sa_family,
                                ipaddr_obj, ntohs(sa->sin_port));
            Py_DECREF(ipaddr_obj);
          }
          break;
        case AF_UNIX:
          arglist = Py_BuildValue("(Oshs)", c, hostname, hostaddr->sa_family,
                                hostaddr->sa_data);
          break;
    #ifdef HAVE_IPV6_SUPPORT
        case AF_INET6:
          { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)hostaddr;
            PyObject *ip6addr_obj = makeip6addr(sa);
            long scope_id = 0;
    #ifdef HAVE_IPV6_RFC2553
    	scope_id = ntohl(sa->sin6_scope_id);
    #endif
            arglist = Py_BuildValue("(Osh(Oiii))", c, hostname, hostaddr->sa_family,
    				ip6addr_obj,
    				ntohs(sa->sin6_port),
    				ntohl(sa->sin6_flowinfo),
    				scope_id);
            Py_DECREF(ip6addr_obj);
          }
          break;
    #endif
        default:
          arglist = Py_BuildValue("(OshO)", c, hostname, hostaddr->sa_family,
                                Py_None);
        }
      }
      else
        arglist = Py_BuildValue("(OshO)", c, hostname, 0, Py_None);
      return _generic_wrapper(c, connect_callback, arglist);
    }
    
    static int
    milter_wrap_helo(SMFICTX *ctx, char *helohost) {
      PyObject *arglist;
      milter_ContextObject *c;
    
      if (helo_callback == NULL) return SMFIS_CONTINUE;
      c = _get_context(ctx);
      if (!c) return SMFIS_TEMPFAIL;
      arglist = Py_BuildValue("(Os)", c, helohost);
      return _generic_wrapper(c, helo_callback, arglist);
    }
    
    static int
    
    generic_env_wrapper(SMFICTX *ctx, PyObject*cb, char **argv) {
    
       PyObject *arglist;
       milter_ContextObject *self;
       int count = 0;
       int i;
       char **p = argv;
    
       if (cb == NULL) return SMFIS_CONTINUE; 
    
       self = _get_context(ctx);
       if (!self) return SMFIS_TEMPFAIL;
    
       /* Count how many strings we've been passed. */
       while (*p++ != NULL) count++;
       /* how to build the value in steps?  Cheat by copying from */
       /* Python/modsupport.c do_mktuple() and do_mkvalue() */
       if ((arglist = PyTuple_New(count+1)) == NULL)
         return _report_exception(self);
       /* Add in the context first */
       Py_INCREF(self);	/* PyTuple_SetItem takes over reference */
       PyTuple_SetItem(arglist, 0, (PyObject *)self);
       /* Now do all the strings */
       for (i=0;i<count;i++) {
         /* There's some error checking performed in do_mkvalue() for a string */
         /* that's not currently done here - it probably should be */
         PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i]));
         if (o == NULL) {	/* out of memory */
           Py_DECREF(arglist);
           return _report_exception(self);
         }
         PyTuple_SetItem(arglist, i + 1, o);
       }
       return _generic_wrapper(self, cb, arglist);
    }
    
    static int
    milter_wrap_envfrom(SMFICTX *ctx, char **argv) {
    
      return generic_env_wrapper(ctx,envfrom_callback,argv);
    
    }
    
    static int
    milter_wrap_envrcpt(SMFICTX *ctx, char **argv) {
    
      return generic_env_wrapper(ctx,envrcpt_callback,argv);
    
    }    
      
    static int
    milter_wrap_header(SMFICTX *ctx, char *headerf, char *headerv) {
       PyObject *arglist;
       milter_ContextObject *c;
    
       if (header_callback == NULL) return SMFIS_CONTINUE;
       c = _get_context(ctx);
       if (!c) return SMFIS_TEMPFAIL;
       arglist = Py_BuildValue("(Oss)", c, headerf, headerv);
       return _generic_wrapper(c, header_callback, arglist);
    }
    
    static int
    
    generic_noarg_wrapper(SMFICTX *ctx,PyObject *cb) {
    
       PyObject *arglist;
       milter_ContextObject *c;
       if (cb == NULL) return SMFIS_CONTINUE;
       c = _get_context(ctx);
       if (!c) return SMFIS_TEMPFAIL;
       arglist = Py_BuildValue("(O)", c);
       return _generic_wrapper(c, cb, arglist);
    }
    
    static int
    milter_wrap_eoh(SMFICTX *ctx) {
    
      return generic_noarg_wrapper(ctx,eoh_callback);
    
    }   
    
    static int
    milter_wrap_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) {
       PyObject *arglist;
       milter_ContextObject *c;
    
       if (body_callback == NULL) return SMFIS_CONTINUE;
       c = _get_context(ctx);
       if (!c) return SMFIS_TEMPFAIL;
       /* Unclear whether this should be s#, z#, or t# */
       arglist = Py_BuildValue("(Os#)", c, bodyp, bodylen);
       return _generic_wrapper(c, body_callback, arglist);
    }
    
    static int
    milter_wrap_eom(SMFICTX *ctx) {
    
      return generic_noarg_wrapper(ctx,eom_callback);
    
    }
    
    static int
    milter_wrap_abort(SMFICTX *ctx) {
      /* libmilter still calls close after abort */
    
      return generic_noarg_wrapper(ctx,abort_callback);
    
    #ifdef SMFIS_ALL_OPTS
    static int
    milter_wrap_unknown(SMFICTX *ctx, const char *cmd) {
       PyObject *arglist;
       milter_ContextObject *c;
    
       if (unknown_callback == NULL) return SMFIS_CONTINUE;
       c = _get_context(ctx);
       if (!c) return SMFIS_TEMPFAIL;
       arglist = Py_BuildValue("(Os)", c, cmd);
       return _generic_wrapper(c, unknown_callback, arglist);
    }
    
    static int
    milter_wrap_data(SMFICTX *ctx) {
      return generic_noarg_wrapper(ctx,data_callback);
    }   
    
    static int
    milter_wrap_negotiate(SMFICTX *ctx,
    	unsigned long f0,
    	unsigned long f1,
    	unsigned long f2,
    	unsigned long f3,
    	unsigned long *pf0,
    	unsigned long *pf1,
    	unsigned long *pf2,
    	unsigned long *pf3) {
      PyObject *arglist, *optlist;
      milter_ContextObject *c;
      int rc;
    
      if (negotiate_callback == NULL) return SMFIS_ALL_OPTS;
      c = _get_context(ctx);
      if (!c)
        return SMFIS_REJECT; // do not contact us again for current connection
      optlist = Py_BuildValue("[kkkk]",f0,f1,f2,f3);
      if (optlist == NULL)
        arglist = NULL;
      else
        arglist = Py_BuildValue("(OO)", c, optlist);
      PyThreadState *t = c->t;
      c->t = 0;	// do not release thread in _generic_wrapper
    
      rc = _generic_wrapper(c, negotiate_callback, arglist);
    
      c->t = t;
      if (rc == SMFIS_CONTINUE) {
    #if 0	// PyArgs_Parse deprecated and going away
        if (!PyArgs_Parse(optlist,"[kkkk]",pf0,pf1,pf2,pf3)) {
          PyErr_Print();
          PyErr_Clear();	/* must clear since not returning to python */
          rc = SMFIS_REJECT;
        }
    #else
        unsigned long *pa[4] = { pf0,pf1,pf2,pf3 };
        unsigned long fa[4] = { f0,f1,f2,f3 };
        int len = PyList_Size(optlist);
        int i;
        for (i = 0; i < 4; ++i) {
          *pa[i] = (i <= len)
          	? PyInt_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
    	: fa[i];
        }
        if (PyErr_Occurred()) {
          PyErr_Print();
          PyErr_Clear();
          rc = SMFIS_REJECT;
        }
    #endif
      }
      else if (rc != SMFIS_ALL_OPTS)
        rc = SMFIS_REJECT;
      Py_DECREF(optlist);
      _release_thread(t);
      return rc;
    }
    #endif
    
    
    static int
    milter_wrap_close(SMFICTX *ctx) {
    
      /* xxfi_close can be called out of order - even before connect.  
       * There may not yet be a private context pointer.  To avoid
       * creating a ThreadContext and allocating a milter context only
       * to destroy them, and to avoid invoking the python close_callback when
       * connect has never been called, we don't use generic_noarg_wrapper here. */
    
      PyObject *cb = close_callback;
      milter_ContextObject *self = smfi_getpriv(ctx);
      int r = SMFIS_CONTINUE;
    
      if (self != NULL) {
        PyThreadState *t = self->t;
        PyEval_AcquireThread(t);
        self->t = 0;
        if (cb != NULL && self->ctx == ctx) {
          PyObject *arglist = Py_BuildValue("(O)", self);
          /* Call python close callback, but do not ReleaseThread, because
           * self->t is NULL */
          r = _generic_wrapper(self, cb, arglist);
        }
        self->ctx = 0;
        smfi_setpriv(ctx,0);
        Py_DECREF(self);
        PyThreadState_Clear(t);
        PyEval_ReleaseThread(t);
        PyThreadState_Delete(t);
    
      return r;
    }
    
    static char milter_register__doc__[] =
    
    "register(name,unknown=,data=,negotiate=) -> None\n\
    
    Registers the milter name with current callbacks, and flags.\n\
    Required before main() is called.";
    
    static PyObject *
    
    milter_register(PyObject *self, PyObject *args, PyObject *kwds) {
    
      static char *kwlist[] = { "name","unknown","data","negotiate", NULL }; 
    
      static PyObject** const cbp[3] =
        { &unknown_callback, &data_callback, &negotiate_callback };
      PyObject *cb[3] = { NULL, NULL, NULL };
      int i;
      if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOO:register", kwlist,
      	&description.xxfi_name, &cb[0],&cb[1],&cb[2]))
         return NULL;
      for (i = 0; i < 3; ++i) {
        PyObject *callback = cb[i];
        if (callback != NULL && callback != Py_None) {
          if (!PyCallable_Check(callback)) {
    	char err[80];
    	sprintf(err,"%s parameter must be callable",kwlist[i]);
    	PyErr_SetString(PyExc_TypeError, err);
    	return NULL;
          }
        }