Updates to the python module, from migs paraz
authoraland <aland>
Tue, 9 Jul 2002 19:16:23 +0000 (19:16 +0000)
committeraland <aland>
Tue, 9 Jul 2002 19:16:23 +0000 (19:16 +0000)
doc/rlm_python [new file with mode: 0644]
raddb/experimental.conf
src/modules/rlm_python/prepaid.py [new file with mode: 0644]
src/modules/rlm_python/prepaid.sql [new file with mode: 0644]
src/modules/rlm_python/radiusd.py [new file with mode: 0644]
src/modules/rlm_python/radiusd_test.py [new file with mode: 0644]
src/modules/rlm_python/rlm_python.c

diff --git a/doc/rlm_python b/doc/rlm_python
new file mode 100644 (file)
index 0000000..ec92aea
--- /dev/null
@@ -0,0 +1,76 @@
+Python module for freeradius
+Copyright 2002 Miguel A Paraz <mparaz@mparaz.com>
+Copyright 2002 Imperium Technology, Inc.
+
+PURPOSE:
+To allow module writers to write modules in a high-level language, 
+for implementation or for prototyping.
+
+REQUIRES:
+Python - tested with 2.2
+
+BUILDING:
+Build with: './configure --with-static-modules=python' since Python does not
+supply a dynamic library by default.
+
+USAGE:
+Make your module available to the Python interpreter by either putting it
+in a standard location, or 'EXPORT PYTHONPATH=$location'.
+
+
+
+
+
+BUGS:
+1. Can't compile statically (./configure --enable-shared=no)  - causes
+SIGSEGV on the first malloc() in main().
+
+Design:
+1. Support for all module functions.
+2. One module per function allowed, for example, from experimental.conf:
+
+       python {
+               mod_instantiate = radiusd_test
+               func_instantiate = instantiate
+
+               mod_authorize = radiusd_test
+               func_authorize = authorize
+
+               mod_accounting = radiusd_test
+               func_accounting = accounting
+
+               mod_preacct = radiusd_test
+               func_preacct = preacct
+
+               mod_detach = radiusd_test
+               func_detach = detach
+
+       }
+
+
+3. Different functions are wrappers around the same core.
+4. func_detach is passed no parameters, returns module return value.
+5. If functions returns None (plain 'return' no return), default to RLM_OK
+6. Python instantation function can return -1 to signal failure and abort
+   startup.
+
+Available to module:
+import radiusd
+radiusd.rad_log(radiusd.L_XXX, message_string)
+radiusd.RLM_XXX
+
+
+
+TODO:
+1. Do we need to support other pair operations beyond set (:=) ?
+2. Should we pass the value pair info as a dict and not a tuple? Faster?
+2. Give access to more radiusd variables like the dictionary.
+3. Give access to other C functions.  
+   Let the Python module deal with the structures directly, instead of
+   letting our C code do it afterwards.
+   What's a good way to represent this?
+
+
+
+
+
index bbbd582..22fd198 100644 (file)
        # value-pairs which are passed on to pairmake().
        #
        python {
-               mod_authorize = radiusd
+               mod_instantiate = radiusd_test
+               func_instantiate = instantiate
+
+               mod_authorize = radiusd_test
                func_authorize = authorize
+
+               mod_accounting = radiusd_test
+               func_accounting = accounting
+
+               mod_preacct = radiusd_test
+               func_preacct = preacct
+
+               mod_detach = radiusd_test
+               func_detach = detach
        }
 
        
diff --git a/src/modules/rlm_python/prepaid.py b/src/modules/rlm_python/prepaid.py
new file mode 100644 (file)
index 0000000..51567b6
--- /dev/null
@@ -0,0 +1,239 @@
+#
+# Example Python module for prepaid usage using MySQL
+
+# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# Copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com>
+# Copyright 2002 Imperium Technology, Inc.
+
+import radiusd
+import MySQLdb
+
+# Configuration
+configDb = 'python'                     # Database name
+configHost = 'localhost'                # Database host
+configUser = 'python'                   # Database user and password
+configPasswd = 'python'         
+
+# xxx Database 
+
+# Globals
+dbHandle = None
+
+def log(level, s):
+  """Log function."""
+  radiusd.radlog(level, 'prepaid.py: ' + s)
+
+def instantiate(p):
+  """Module Instantiation.  0 for success, -1 for failure.
+  p is a dummy variable here."""
+  global dbHandle
+
+  p = p
+
+  try:
+    dbHandle = MySQLdb.connect(db=configDb, host=configHost,
+                               user=configUser, passwd=configPasswd)
+
+  except MySQLdb.OperationalError, e:
+    # Report the error and return -1 for failure.
+    # xxx A more advanced module would retry the database.
+    log(radiusd.L_ERR, str(e))
+    return -1
+
+  log(radiusd.L_INFO, 'db connection: ' + str(dbHandle))
+
+  return 0
+
+
+def authorize(authData):
+  """Authorization and authentication are done in one step."""
+
+  # Extract the data we need.
+  userName = None
+  userPasswd = None
+  
+  for t in authData:
+    if t[0] == 'User-Name':
+      userName = t[1]
+    elif t[0] == 'Password':
+      userPasswd = t[1]
+
+  # Build and log the SQL statement
+  # radiusd puts double quotes (") around the string representation of
+  # the RADIUS packet.
+  sql = 'select passwd, maxseconds from users where username = ' +  userName
+  
+  log(radiusd.L_DBG, sql)
+
+  # Get a cursor
+  # xxx Or should this be one cursor all throughout?
+  try:
+    dbCursor = dbHandle.cursor()
+  except MySQLdb.OperationalError, e:
+    log(radiusd.L_ERR, str(e))
+    return radiusd.RLM_MODULE_FAIL
+
+  # Execute the SQL statement
+  try:
+    dbCursor.execute(sql)
+  except MySQLdb.OperationalError, e:
+    log(radiusd.L_ERR, str(e))
+    dbCursor.close()
+    return radiusd.RLM_MODULE_FAIL
+  
+  # Get the result. (passwd, maxseconds)
+  result = dbCursor.fetchone()
+  if not result:
+    # User not found
+    log(radiusd.L_INFO, 'user not found: ' + userName)
+    dbCursor.close()
+    return radiusd.RLM_MODULE_NOTFOUND
+
+
+  # Compare passwords
+  # Ignore the quotes around userPasswd.
+  if result[0] != userPasswd[1:-1]:
+    log(radiusd.L_DBG, 'user password mismatch: ' + userName)
+    return radiusd.RLM_MODULE_REJECT
+
+  maxSeconds = result[1]
+
+  # Compute their session limit
+  
+  # Build and log the SQL statement
+  sql = 'select sum(seconds) from sessions where username = ' + userName
+
+  log(radiusd.L_DBG, sql)
+  
+  # Execute the SQL statement
+  try:
+    dbCursor.execute(sql)
+  except MySQLdb.OperationalError, e:
+    log(radiusd.L_ERR, str(e))
+    dbCursor.close()
+    return radiusd.RLM_MODULE_FAIL
+  
+  # Get the result. (sum,)
+  result = dbCursor.fetchone()
+  if (not result) or (not result[0]):
+    # No usage yet
+    secondsUsed = 0
+  else:
+    secondsUsed = result[0]
+
+  # Done with cursor
+  dbCursor.close()
+
+  # Note that MySQL returns the result of SUM() as a float.
+  sessionTimeout = maxSeconds - int(secondsUsed)
+  
+  if sessionTimeout <= 0:
+    # No more time, reject outright
+    log(radiusd.L_INFO, 'user out of time: ' + userName)    
+    return radiusd.RLM_MODULE_REJECT
+
+  # Log the success
+  log(radiusd.L_DBG, 'user accepted: %s, %d seconds' %
+      (userName, sessionTimeout))
+
+  # We are adding to the RADIUS packet
+  # Note that the session timeout integer must be converted to string.
+  # We need to set an Auth-Type.
+
+  return (radiusd.RLM_MODULE_UPDATED,
+          (('Session-Timeout', str(sessionTimeout)),),
+          (('Auth-Type', 'python'),))
+
+    
+
+def authenticate(p):
+  p = p
+  return radiusd.RLM_MODULE_OK
+
+
+def preacct(p):
+  p = p
+  return radiusd.RLM_MODULE_OK
+
+
+def accounting(acctData):
+  """Accounting."""
+  # Extract the data we need.
+
+  userName = None
+  acctSessionTime = None
+  acctStatusType = None
+
+  # xxx A dict would make this nice.
+  for t in acctData:
+    if t[0] == 'User-Name':
+      userName = t[1]
+    elif t[0] == 'Acct-Session-Time':
+      acctSessionTime = t[1]
+    elif t[0] == 'Acct-Status-Type':
+      acctStatusType = t[1]
+
+
+  # We will not deal with Start for now.
+  # We may later, for simultaneous checks and the like.
+  if acctStatusType == 'Start':
+    return radiusd.RLM_MODULE_OK
+  
+  # Build and log the SQL statement
+  # radiusd puts double quotes (") around the string representation of
+  # the RADIUS packet.
+  #
+  # xxx This is simplistic as it does not record the time, etc.
+  # 
+  sql = 'insert into sessions (username, seconds) values (%s, %d)' % \
+        (userName, int(acctSessionTime))
+  
+  log(radiusd.L_DBG, sql)
+
+  # Get a cursor
+  # xxx Or should this be one cursor all throughout?
+  try:
+    dbCursor = dbHandle.cursor()
+  except MySQLdb.OperationalError, e:
+    log(radiusd.L_ERR, str(e))
+    return radiusd.RLM_MODULE_FAIL
+
+  # Execute the SQL statement
+  try:
+    dbCursor.execute(sql)
+  except MySQLdb.OperationalError, e:
+    log(radiusd.L_ERR, str(e))
+    dbCursor.close()
+    return radiusd.RLM_MODULE_FAIL
+
+  
+  return radiusd.RLM_MODULE_OK
+
+
+def detach():
+  """Detach and clean up."""
+  # Shut down the database connection.
+  global dbHandle
+  log(radiusd.L_DBG, 'closing database handle: ' + str(dbHandle))
+  dbHandle.close()
+
+  return radiusd.RLM_MODULE_OK
+
+
+
+# Test the modules 
+if __name__ == '__main__':
+  instantiate(None)
+  print authorize((('User-Name', '"map"'), ('User-Password', '"abc"')))
diff --git a/src/modules/rlm_python/prepaid.sql b/src/modules/rlm_python/prepaid.sql
new file mode 100644 (file)
index 0000000..3e9ffed
--- /dev/null
@@ -0,0 +1,41 @@
+# MySQL dump 8.13
+#
+# Host: localhost    Database: python
+#--------------------------------------------------------
+# Server version       3.23.36
+
+#
+# Table structure for table 'sessions'
+#
+
+CREATE TABLE sessions (
+  username char(32) default NULL,
+  seconds int(11) default NULL
+) TYPE=MyISAM;
+
+#
+# Dumping data for table 'sessions'
+#
+
+INSERT INTO sessions VALUES ('map',10);
+INSERT INTO sessions VALUES ('map',10);
+INSERT INTO sessions VALUES ('map',10);
+INSERT INTO sessions VALUES ('map',10);
+
+#
+# Table structure for table 'users'
+#
+
+CREATE TABLE users (
+  username char(32) NOT NULL default '',
+  passwd char(32) default NULL,
+  maxseconds int(11) default NULL,
+  PRIMARY KEY  (username)
+) TYPE=MyISAM;
+
+#
+# Dumping data for table 'users'
+#
+
+INSERT INTO users VALUES ('map','abc',100);
+
diff --git a/src/modules/rlm_python/radiusd.py b/src/modules/rlm_python/radiusd.py
new file mode 100644 (file)
index 0000000..08fe100
--- /dev/null
@@ -0,0 +1,42 @@
+#
+# Definitions for RADIUS programs
+#
+# Copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com>
+#
+# This should only be used when testing modules.
+# Inside freeradius, the 'radiusd' Python module is created by the C module
+# and the definitions are automatically created.
+
+# from modules.h
+
+RLM_MODULE_REJECT = 0
+RLM_MODULE_FAIL = 1
+RLM_MODULE_OK = 2
+RLM_MODULE_HANDLED = 3
+RLM_MODULE_INVALID = 4
+RLM_MODULE_USERLOCK = 5
+RLM_MODULE_NOTFOUND = 6
+RLM_MODULE_NOOP = 7    
+RLM_MODULE_UPDATED = 8
+RLM_MODULE_NUMCODES = 9
+
+
+# from radiusd.h
+L_DBG = 1
+L_AUTH = 2
+L_INFO = 3
+L_ERR = 4
+L_PROXY        = 5
+L_CONS = 128
+
+
+# log function
+def radlog(level, msg):
+    import sys
+    sys.stdout.write(msg + '\n')
+
+    level = level
+  
+
+
+  
diff --git a/src/modules/rlm_python/radiusd_test.py b/src/modules/rlm_python/radiusd_test.py
new file mode 100644 (file)
index 0000000..2e24af5
--- /dev/null
@@ -0,0 +1,35 @@
+#
+# Python module test
+# Miguel A.L. Paraz <mparaz@mparaz.com>
+
+import radiusd
+
+def instantiate(p):
+  print "*** instantiate ***"
+  print p
+
+def authorize(p):
+  print "*** authorize ***"
+  print
+  radiusd.radlog(radiusd.L_INFO, '*** radlog call in authorize ***')
+  print
+  print p 
+  return radiusd.RLM_MODULE_OK
+
+def preacct(p):
+  print "*** preacct ***"
+  print p 
+  return radiusd.RLM_MODULE_OK
+
+def accounting(p):
+  print "*** accounting ***"
+  radiusd.radlog(radiusd.L_INFO, '*** radlog call in accounting (0) ***')
+  print
+  print p 
+  return radiusd.RLM_MODULE_OK
+
+
+def detach():
+  print "*** goodbye from radiusd_test.py ***"
+  return radiusd.RLM_MODULE_OK
+
index 572061a..af64a77 100644 (file)
  *
  * Copyright 2000  The FreeRADIUS server project
  * Copyright 2002  Miguel A.L. Paraz <mparaz@mparaz.com>
+ * Copyright 2002  Imperium Technology, Inc.
  */
 
+#include <Python.h>
+
 #include "autoconf.h"
 #include "libradius.h"
 
 #include "modules.h"
 #include "conffile.h"
 
-#include <Python.h>
-
-#if 0
 static const char rcsid[] = "$Id$";
-#endif
 
 /*
  *     Define a structure for our module configuration.
@@ -45,15 +44,52 @@ static const char rcsid[] = "$Id$";
  */
 typedef struct rlm_python_t {
     /* Config section */
-    char               *mod_authorize;  /* Name of authorization module */
-    char               *func_authorize; /* Name of authorization function */
+
+    /* Names of modules */
+    char
+        *mod_instantiate,
+        *mod_authorize, 
+       *mod_authenticate,
+       *mod_preacct,
+       *mod_accounting,
+       *mod_checksimul,
+       *mod_detach,
+
+    /* Names of functions */
+        *func_instantiate,
+        *func_authorize, 
+       *func_authenticate,
+       *func_preacct,
+       *func_accounting,
+       *func_checksimul,
+       *func_detach;
+
+
     /* End Config section */
 
-    /* xxx To keep things simple, all functions should initially be
-     * xxx in one module.
-     */
 
-    PyObject *pModule, *pFunc_authorize;
+    /* Python objects for modules */
+    PyObject
+        *pModule_builtin,
+        *pModule_instantiate,
+        *pModule_authorize,
+       *pModule_authenticate,
+       *pModule_preacct,
+       *pModule_accounting,
+       *pModule_checksimul,
+       *pModule_detach,
+
+
+       /* Functions */
+
+       *pFunc_instantiate,
+       *pFunc_authorize,
+       *pFunc_authenticate,
+       *pFunc_preacct,
+       *pFunc_accounting,
+       *pFunc_checksimul,
+       *pFunc_detach;
+
 } rlm_python_t;
 
 /*
@@ -65,29 +101,67 @@ typedef struct rlm_python_t {
  *     to the strdup'd string into 'config.string'.  This gets around
  *     buffer over-flows.
  */
-
-#if 0
 static CONF_PARSER module_config[] = {
-  { "integer", PW_TYPE_INTEGER,    offsetof(rlm_python_t,value), NULL,   "1" },
-  { "boolean", PW_TYPE_BOOLEAN,    offsetof(rlm_python_t,boolean), NULL, "no"},
-  { "string",  PW_TYPE_STRING_PTR, offsetof(rlm_python_t,string), NULL,  NULL},
-  { "ipaddr",  PW_TYPE_IPADDR,     offsetof(rlm_python_t,ipaddr), NULL,  "*" },
-
-  { NULL, -1, 0, NULL, NULL }          /* end the list */
-};
-
-#else
+  { "mod_instantiate",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, mod_instantiate), NULL,  NULL},
+  { "func_instantiate",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, func_instantiate), NULL,  NULL},
 
-static CONF_PARSER module_config[] = {
   { "mod_authorize",  PW_TYPE_STRING_PTR,
-    offsetof(rlm_python_t,mod_authorize), NULL,  NULL},
+    offsetof(rlm_python_t, mod_authorize), NULL,  NULL},
   { "func_authorize",  PW_TYPE_STRING_PTR,
-    offsetof(rlm_python_t,func_authorize), NULL,  NULL},
+    offsetof(rlm_python_t, func_authorize), NULL,  NULL},
+
+  { "mod_authenticate",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, mod_authenticate), NULL,  NULL},
+  { "func_authenticate",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, func_authenticate), NULL,  NULL},
+
+  { "mod_preacct",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, mod_preacct), NULL,  NULL},
+  { "func_preacct",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, func_preacct), NULL,  NULL},
+
+  { "mod_accounting",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, mod_accounting), NULL,  NULL},
+  { "func_accounting",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, func_accounting), NULL,  NULL},
+
+  { "mod_checksimul",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, mod_checksimul), NULL,  NULL},
+  { "func_checksimul",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, func_checksimul), NULL,  NULL},
+
+  { "mod_detach",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, mod_detach), NULL,  NULL},
+  { "func_detach",  PW_TYPE_STRING_PTR,
+    offsetof(rlm_python_t, func_detach), NULL,  NULL},
+
 
   { NULL, -1, 0, NULL, NULL }          /* end the list */
 };
-#endif
 
+/*
+ * radiusd Python functions
+ */
+
+/* radlog wrapper */
+static PyObject *radlog_py(const PyObject *self, PyObject *args) {
+    int status;
+    char *msg;
+
+    if (!PyArg_ParseTuple(args, "is", &status, &msg)) {
+       return NULL;
+    }
+
+    radlog(status, msg);
+    return Py_None;
+}
+
+static PyMethodDef radiusd_methods[] = {
+    {"radlog", (PyCFunction)radlog_py, METH_VARARGS, "freeradius radlog()."},
+    {NULL, NULL, 0, NULL}
+};
 
 /*
  *     Do any per-module initialization.  e.g. set up connections
@@ -100,244 +174,293 @@ static CONF_PARSER module_config[] = {
 static int python_init(void)
 {
     /*
-     * Initialize Python interpreter
+     * Initialize Python interpreter. Fatal error if this fails.
      */
     Py_Initialize();
 
+    radlog(L_DBG, "python_init done");
+
     return 0;
 }
 
-/*
- *     Do any per-module initialization that is separate to each
- *     configured instance of the module.  e.g. set up connections
- *     to external databases, read configuration files, set up
- *     dictionary entries, etc.
- *
- *     If configuration information is given in the config section
- *     that must be referenced in later calls, store a handle to it
- *     in *instance otherwise put a null pointer there.
- */
-static int python_instantiate(CONF_SECTION *conf, void **instance)
-{
-       rlm_python_t *data;
-       PyObject *pName;
-       
-       /*
-        *      Set up a storage area for instance data
-        */
-       data = rad_malloc(sizeof(*data));
+/* Extract string representation of Python error. */
+static void python_error(void) {
+    PyObject *pType, *pValue, *pTraceback, *pStr1, *pStr2;
 
-       /*
-        *      If the configuration parameters can't be parsed, then
-        *      fail.
-        */
-       if (cf_section_parse(conf, data, module_config) < 0) {
-               free(data);
-               return -1;
-       }
-       
-       *instance = data;
+    PyErr_Fetch(&pType, &pValue, &pTraceback);
+    pStr1 = PyObject_Str(pType);
+    pStr2 = PyObject_Str(pValue);
+    
+    radlog(L_ERR, "%s: %s\n",
+          PyString_AsString(pStr1), PyString_AsString(pStr2));
+}
 
-       pName = PyString_FromString(data->mod_authorize);
+/* Tuple to value pair conversion */
+static void add_vp_tuple(VALUE_PAIR **vpp, PyObject *pValue,
+                        const char *function_name) {
+    int i, outertuplesize;
+    VALUE_PAIR *vp;
 
-       /* Import module */
-       data->pModule = PyImport_Import(pName);
-       if (data->pModule != NULL) {
-           PyObject *pDict;
+    /* If the Python function gave us None for the tuple, then just return. */
+    if (pValue == Py_None) {
+       return;
+    }
+    
+    if (!PyTuple_Check(pValue)) {
+       radlog(L_ERR, "%s: non-tuple passed", function_name);
+    }
 
-           pDict = PyModule_GetDict(data->pModule);
-           /* pDict: borrowed reference */
+    /* Get the tuple size. */
+    outertuplesize = PyTuple_Size(pValue);
+    
+    for (i = 0; i < outertuplesize; i++) {
+       PyObject *pTupleElement = PyTuple_GetItem(pValue, i);
+                   
+       if ((pTupleElement != NULL) &&
+           (PyTuple_Check(pTupleElement))) {
 
-           data->pFunc_authorize =
-               PyDict_GetItemString(pDict, data->func_authorize);
-           /* pFunc: Borrowed reference */
+           /* Check if it's a pair */
+           int tuplesize;
+                       
+           if ((tuplesize = PyTuple_Size(pTupleElement)) != 2) {
+               radlog(L_ERR, "%s: tuple element %d is a tuple "
+                      " of size %d. must be 2\n", function_name,
+                      i, tuplesize);
+           }
+           else {
+               PyObject *pString1, *pString2;
+                           
+               pString1 = PyTuple_GetItem(pTupleElement, 0);
+               pString2 = PyTuple_GetItem(pTupleElement, 1);
+
+               /* xxx PyString_Check does not compile here */
+               if  ((pString1 != NULL) &&
+                    (pString2 != NULL) &&
+                    PyObject_TypeCheck(pString1,&PyString_Type) &&
+                    PyObject_TypeCheck(pString2,&PyString_Type)) {
+
+
+                   const char *s1, *s2;
+                               
+                   /* pairmake() will convert and find any
+                    * errors in the pair.
+                    */
+
+                   s1 = PyString_AsString(pString1);
+                   s2 = PyString_AsString(pString2);
+
+                   if ((s1 != NULL) && (s2 != NULL)) {
+                       radlog(L_DBG, "%s: %s = %s ",
+                              function_name, s1, s2);
+
+                       /* xxx Might need to support other T_OP */
+                       vp = pairmake(s1, s2, T_OP_EQ);
+                       if (vp != NULL) {
+                           pairadd(vpp, vp);
+                           radlog(L_DBG, "%s: s1, s2 OK\n",
+                                  function_name);
+                       }
+                       else {
+                           radlog(L_DBG, "%s: s1, s2 FAILED\n",
+                                  function_name);
+                       }
+                   }
+                   else {
+                       radlog(L_ERR, "%s: string conv failed\n",
+                              function_name);
+                   }
+
+               }
+               else {
+                   radlog(L_ERR, "%s: tuple element %d must be "
+                          "(string, string)", function_name, i);
+               }
+           }
        }
        else {
-           /* xxx Change this to dump error message to log xxx */
-           PyErr_Print();
-
-           radlog(L_ERR, "Failed to load \"%s\"\n", data->mod_authorize);
-           return -1;
+           radlog(L_ERR, "%s: tuple element %d is not a tuple\n",
+                  function_name, i);
        }
+    }
 
-       /* xxx Should we check if function is callable now?
-        * xxx or later when it is used, since it can change...
-        */ 
-
-       
-       return 0;
 }
 
-/* Pass the value-pair print strings in a tuple.
-   xxx We're not checking the errors. If we have errors, what do we do?
-*/
-static int python_authorize(void *instance, REQUEST *request)
+/* This is the core Python function that the others wrap around.
+ * Pass the value-pair print strings in a tuple.
+ * xxx We're not checking the errors. If we have errors, what do we do?
+ */
+
+static int python_function(REQUEST *request,
+                          PyObject *pFunc, const char *function_name)
 {
+#define BUF_SIZE 1024
+
+    char buf[BUF_SIZE];                /* same size as vp_print buffer */
+
     VALUE_PAIR *vp;
-    char buf[1024];            /* same size as vp_print buffer */
+
     PyObject *pValue, *pValuePairContainer, **pValueHolder, **pValueHolderPtr;
     int i, n_tuple, return_value;
     
-#define inst ((struct rlm_python_t *)instance)
-
-    /* Default return value is failure */
-    return_value = -1;
+    /* Return with "OK, continue" if the function is not defined. */
+    if (pFunc == NULL) {
+       return RLM_MODULE_OK;
+    }
 
+    /* Default return value is "OK, continue" */
+    return_value = RLM_MODULE_OK;
+    
     /* We will pass a tuple containing (name, value) tuples 
      * We can safely use the Python function to build up a tuple,
      * since the tuple is not used elsewhere.
      *
      * Determine the size of our tuple by walking through the packet.
+     * If request is NULL, pass None.
      */
     n_tuple = 0;
 
-    for (vp = request->packet->vps; vp; vp = vp->next) {
-       n_tuple++;
+    if (request != NULL) {
+       for (vp = request->packet->vps; vp; vp = vp->next) {
+           n_tuple++;
+       }
     }
        
     /* Create the tuple and a holder for the pointers, so that we can
      * decref more efficiently later without the overhead of reading
      * the tuple.
+     *
+     * We use malloc() instead of the Python memory allocator since we
+     * are not embedded.
      */
-    pValuePairContainer = PyTuple_New(n_tuple);
+
     if (NULL == (pValueHolder = pValueHolderPtr =
                 malloc(sizeof(PyObject *) * n_tuple))) {
-       
-       radlog(L_ERR, "malloc of %d bytes failed\n",
-              sizeof(PyObject *) * n_tuple);
-       
+           
+       radlog(L_ERR, "%s: malloc of %d bytes failed\n",
+              function_name, sizeof(PyObject *) * n_tuple);
+           
        return -1;
     }
+
+    if (n_tuple == 0) {
+       pValuePairContainer = Py_None;
+    }
+    else {
+       pValuePairContainer = PyTuple_New(n_tuple);
     
-    i = 0;
-    for (vp = request->packet->vps; vp; vp = vp->next) {
-       PyObject *pValuePair, *pString1, *pString2;
+       i = 0;
+       for (vp = request->packet->vps; vp; vp = vp->next) {
+           PyObject *pValuePair, *pString1, *pString2;
        
-       /* The inside tuple has two only: */
-       pValuePair = PyTuple_New(2);
+           /* The inside tuple has two only: */
+           pValuePair = PyTuple_New(2);
        
-       /* The name. logic from vp_prints, lib/print.c */
-       if (vp->flags.has_tag) {
-           sprintf(buf, "%s:%d", vp->name, vp->flags.tag);
-       }
-       else {
-           strcpy(buf, vp->name);
-       }
+           /* The name. logic from vp_prints, lib/print.c */
+           if (vp->flags.has_tag) {
+               snprintf(buf, BUF_SIZE, "%s:%d", vp->name, vp->flags.tag);
+           }
+           else {
+               strcpy(buf, vp->name);
+           }
        
-       pString1 = PyString_FromString(buf);
-       PyTuple_SetItem(pValuePair, 0, pString1);
+           pString1 = PyString_FromString(buf);
+           PyTuple_SetItem(pValuePair, 0, pString1);
        
        
-       /* The value. Use delimiter - don't know what that means */
-       vp_prints_value(buf, sizeof(buf), vp, 1);
-       pString2 = PyString_FromString(buf);
-       PyTuple_SetItem(pValuePair, 1, pString2);
+           /* The value. Use delimiter - don't know what that means */
+           vp_prints_value(buf, sizeof(buf), vp, 1);
+           pString2 = PyString_FromString(buf);
+           PyTuple_SetItem(pValuePair, 1, pString2);
        
-       /* Put the tuple inside the container */
-       PyTuple_SetItem(pValuePairContainer, i++, pValuePair);
+           /* Put the tuple inside the container */
+           PyTuple_SetItem(pValuePairContainer, i++, pValuePair);
        
-       /* Store the pointer in our malloc() storage */
-       *pValueHolderPtr++ = pValuePair;
+           /* Store the pointer in our malloc() storage */
+           *pValueHolderPtr++ = pValuePair;
+       }
     }
+
     
     /* Call Python function.
-     * xxx need to make visible wrappers for functions such as radlog
      */
     
-    if (inst->pFunc_authorize && PyCallable_Check(inst->pFunc_authorize)) {
+    if (pFunc && PyCallable_Check(pFunc)) {
        PyObject *pArgs;
        
-       /* xxx this should have error checking xxx */
-       
        /* call the function with a singleton tuple containing the
-        * value-pair container tuple.
+        * container tuple.
         */
-       pArgs = PyTuple_New(1);
-       PyTuple_SetItem(pArgs, 0, pValuePairContainer);
-       
-       pValue = PyObject_CallObject(inst->pFunc_authorize, pArgs);
+
+       if ((pArgs = PyTuple_New(1)) == NULL) {
+           radlog(L_ERR, "%s: could not create tuple", function_name);
+           return -1;
+       }
+       if ((PyTuple_SetItem(pArgs, 0, pValuePairContainer)) != 0) {
+           radlog(L_ERR, "%s: could not set tuple item", function_name);
+           return -1;
+       }
        
-       if (pValue == NULL) {
-           PyErr_Print();
+       if ((pValue = PyObject_CallObject(pFunc, pArgs)) == NULL) {
+           radlog(L_ERR, "%s: function call failed", function_name);
+           python_error();
            return -1;
        }
+       
+       /* The function returns either:
+        *  1. tuple containing the integer return value,
+        *  then the integer reply code (or None to not set),
+        *  then the string tuples to build the reply with.
+        *     (returnvalue, (p1, s1), (p2, s2))
+        *
+        *  2. the function return value alone
+        *
+        *  3. None - default return value is set
+        *
+        * xxx This code is messy!
+        */
 
-       /* Returns a tuple for the function return value,
-        * then the strings to build the reply with. */
        if (PyTuple_Check(pValue)) {
            PyObject *pTupleInt;
-           int n;
 
-           n = PyTuple_Size(pValue);
+           if (PyTuple_Size(pValue) != 3) {
+               radlog(L_ERR, "%s: tuple must be " \
+                      "(return, replyTuple, configTuple)",
+                      function_name);
 
-
-           if (n == 0) {
-               radlog(L_ERR, "tuple must have at least one element");
-           }
-           else if (pTupleInt = PyTuple_GetItem(pValue, 0),
-                    !PyInt_Check(pTupleInt)) {
-               radlog(L_ERR, "first tuple element not an integer");
            }
            else {
-               return_value = PyInt_AsLong(pTupleInt);
-               
-               
-               
-               for (i = 1; i < n; i++) {
-                   PyObject *pTupleElement = PyTuple_GetItem(pValue, i);
+               pTupleInt = PyTuple_GetItem(pValue, 0);
+
+               if ((pTupleInt == NULL) || !PyInt_Check(pTupleInt)) {
+                   radlog(L_ERR, "%s: first tuple element not an integer",
+                          function_name);
+               }
+               else {
+                   /* Now have the return value */
+                   return_value = PyInt_AsLong(pTupleInt);
                    
-                   if (PyTuple_Check(pTupleElement)) {
-                       /* Check if it's a pair */
-                       int m;
-                       
-                       if ((m = PyTuple_Size(pTupleElement)) != 2) {
-                           radlog(L_ERR, "tuple element %d is a tuple "
-                                  " of size %d. must be 2\n", i, m);
-                       }
-                       else {
-                           PyObject *pString1, *pString2;
-                           
-                           pString1 = PyTuple_GetItem(pTupleElement, 0);
-                           pString2 = PyTuple_GetItem(pTupleElement, 1);
+                   /* Reply item tuple */
+                   add_vp_tuple(&request->reply->vps,
+                                PyTuple_GetItem(pValue, 1), function_name);
 
-                           if (PyString_Check(pString1) &&
-                               PyString_Check(pString2)) {
-                               char *s1, *s2;
-                               
-                               /* pairmake() will convert and find any
-                                * errors in the pair.
-                                */
-
-                               s1 = PyString_AsString(pString1);
-                               s2 = PyString_AsString(pString2);
-
-                               radlog(L_DBG, "python: %s = %s ", s1, s2);
-
-                               vp = pairmake(s1, s2, T_OP_EQ);
-                               if (vp != NULL) {
-                                   pairadd(&request->packet->vps, vp);
-                                   radlog(L_DBG, "OK\n");
-                               }
-                               else {
-                                   radlog(L_DBG, "FAILED\n");
-                               }
-
-                           }
-                           else {
-                               radlog(L_ERR, "tuple element %d must be "
-                                      "(string, string)", i);
-                           }
-                       }
-                   }
-                   else {
-                       radlog(L_ERR, "tuple element %d is not a tuple\n", i);
-                   }
+                   /* Config item tuple */
+                   add_vp_tuple(&request->config_items,
+                                PyTuple_GetItem(pValue, 2), function_name);
                }
            }
        }
+       else if (PyInt_Check(pValue)) {
+           /* Just an integer */
+           return_value = PyInt_AsLong(pValue);
+       }
+       else if (pValue == Py_None) {
+           /* returned 'None', return value defaults to "OK, continue." */
+           return_value = RLM_MODULE_OK;
+       }
        else {
-           /* Not a tuple */
-           radlog(L_ERR, "authorize function did not return a tuple\n");
+           /* Not tuple or None */
+           radlog(L_ERR, "%s function did not return a tuple or None\n",
+                  function_name);
        }
 
 
@@ -362,17 +485,434 @@ static int python_authorize(void *instance, REQUEST *request)
     
     /* pDict and pFunc are borrowed and must not be Py_DECREF-ed */
 
+    /* Free pairs if we are rejecting.
+     * xxx Shouldn't the core do that?
+     */
+    
+    if ((return_value == RLM_MODULE_REJECT) && (request != NULL)) {
+       pairfree(&(request->reply->vps));
+    }
     
     /* Return the specified by the Python module */
     return return_value;
 }
 
 
+/*
+ *     Do any per-module initialization that is separate to each
+ *     configured instance of the module.  e.g. set up connections
+ *     to external databases, read configuration files, set up
+ *     dictionary entries, etc.
+ *
+ *     If configuration information is given in the config section
+ *     that must be referenced in later calls, store a handle to it
+ *     in *instance otherwise put a null pointer there.
+ *
+ */
+static int python_instantiate(CONF_SECTION *conf, void **instance)
+{
+    rlm_python_t *data;
+    PyObject *pName, *module;
+
+    /*
+        *      Set up a storage area for instance data
+        */
+    data = rad_malloc(sizeof(*data));
+
+    /*
+        *      If the configuration parameters can't be parsed, then
+        *      fail.
+        */
+    if (cf_section_parse(conf, data, module_config) < 0) {
+       free(data);
+       return -1;
+    }
+       
+    *instance = data;
+
+
+    /*
+     * Setup our 'radiusd' module.
+     */
+    
+    /* Code */
+    if ((module = data->pModule_builtin =
+        Py_InitModule3("radiusd", radiusd_methods,
+                       "FreeRADIUS Module.")) == NULL) {
+
+       radlog(L_ERR, "Python Py_InitModule3 failed");
+    }
+    
+    /*
+     * Constants
+     * xxx Find a way to automatically update this when C source changes.
+     */
+    if ((PyModule_AddIntConstant(module, "L_DBG", L_DBG) == -1) ||
+       (PyModule_AddIntConstant(module, "L_AUTH", L_AUTH) == -1) ||
+       (PyModule_AddIntConstant(module, "L_INFO", L_INFO) == -1) ||
+       (PyModule_AddIntConstant(module, "L_ERR", L_ERR) == -1) ||
+       (PyModule_AddIntConstant(module, "L_PROXY", L_PROXY) == -1) ||
+       (PyModule_AddIntConstant(module, "L_CONS", L_CONS) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_REJECT",
+                                RLM_MODULE_REJECT) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_FAIL",
+                                RLM_MODULE_FAIL) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_OK",
+                                RLM_MODULE_OK) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_HANDLED",
+                                RLM_MODULE_HANDLED) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_INVALID",
+                                RLM_MODULE_INVALID) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_USERLOCK",
+                                RLM_MODULE_USERLOCK) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_NOTFOUND",
+                                RLM_MODULE_NOTFOUND) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_NOOP",
+                                RLM_MODULE_NOOP) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_UPDATED",
+                                RLM_MODULE_UPDATED) == -1) ||
+       (PyModule_AddIntConstant(module, "RLM_MODULE_NUMCODES",
+                                RLM_MODULE_NUMCODES) == -1)) {
+
+       radlog(L_ERR, "Python AddIntConstant failed");
+       return -1;
+    }
+
+
+    /*
+     * Import user modules.
+     * xxx There should be a more elegant way of writing this.
+     * xxx Loop through a pointer array for the objects and name strings?
+     */
+
+    if ((data->mod_instantiate == NULL) || (data->func_instantiate == NULL)) {
+       data->pFunc_instantiate = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_instantiate);     
+       data->pModule_instantiate = PyImport_Import(pName);
+       if (data->pModule_instantiate != NULL) {
+           PyObject *pDict;
+
+           pDict = PyModule_GetDict(data->pModule_instantiate);
+           /* pDict: borrowed reference */
+
+           data->pFunc_instantiate =
+               PyDict_GetItemString(pDict, data->func_instantiate);
+           /* pFunc: Borrowed reference */
+       }
+
+       else {
+           python_error();
+
+           /* No need to decrement references because we are exiting. */
+
+           radlog(L_ERR, "Failed to import python module \"%s\"\n",
+                  data->mod_instantiate);
+           return -1;
+       }
+    }
+
+    if ((data->mod_authenticate == NULL) || (data->func_authenticate == NULL)) {
+       data->pFunc_authenticate = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_authenticate);
+
+       /* Import modules */
+       data->pModule_authenticate = PyImport_Import(pName);
+       if (data->pModule_authenticate != NULL) {
+           PyObject *pDict;
+
+           pDict = PyModule_GetDict(data->pModule_authenticate);
+           /* pDict: borrowed reference */
+
+           data->pFunc_authenticate =
+               PyDict_GetItemString(pDict, data->func_authenticate);
+           /* pFunc: Borrowed reference */
+       }
+       else {
+           python_error();
+
+           /* No need to decrement references because we are exiting. */
+
+           radlog(L_ERR, "Failed to import Python module \"%s\"\n",
+                  data->mod_authenticate);
+           return -1;
+       }
+    }
+
+    
+    if ((data->mod_authorize == NULL) || (data->func_authorize == NULL)) {
+       data->pFunc_authorize = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_authorize);       
+       data->pModule_authorize = PyImport_Import(pName);
+       if (data->pModule_authorize != NULL) {
+           PyObject *pDict;
+
+           pDict = PyModule_GetDict(data->pModule_authorize);
+           /* pDict: borrowed reference */
+
+           data->pFunc_authorize =
+               PyDict_GetItemString(pDict, data->func_authorize);
+           /* pFunc: Borrowed reference */
+       }
+
+       else {
+           python_error();
+
+           /* No need to decrement references because we are exiting. */
+
+           radlog(L_ERR, "Failed to import python module \"%s\"\n",
+                  data->mod_authorize);
+           return -1;
+       }
+    }
+
+    if ((data->mod_authenticate == NULL) || (data->func_authenticate == NULL)) {
+       data->pFunc_authenticate = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_authenticate);
+
+       /* Import modules */
+       data->pModule_authenticate = PyImport_Import(pName);
+       if (data->pModule_authenticate != NULL) {
+           PyObject *pDict;
+
+           pDict = PyModule_GetDict(data->pModule_authenticate);
+           /* pDict: borrowed reference */
+
+           data->pFunc_authenticate =
+               PyDict_GetItemString(pDict, data->func_authenticate);
+           /* pFunc: Borrowed reference */
+       }
+       else {
+           python_error();
+
+           /* No need to decrement references because we are exiting. */
+
+           radlog(L_ERR, "Failed to import Python module \"%s\"\n",
+                  data->mod_authenticate);
+           return -1;
+       }
+    }
+
+    if ((data->mod_preacct == NULL) || (data->func_preacct == NULL)) {
+       data->pFunc_preacct = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_preacct);
+
+       /* Import modules */
+       data->pModule_preacct = PyImport_Import(pName);
+       if (data->pModule_preacct != NULL) {
+           PyObject *pDict;
+               
+           pDict = PyModule_GetDict(data->pModule_preacct);
+           /* pDict: borrowed reference */
+               
+           data->pFunc_preacct =
+               PyDict_GetItemString(pDict, data->func_preacct);
+           /* pFunc: Borrowed reference */
+       }
+       else {
+           python_error();
+
+           /* No need to decrement references because we are exiting. */
+               
+           radlog(L_ERR, "Failed to import Python module \"%s\"\n",
+                  data->mod_preacct);
+           return -1;
+       }
+    }
+
+
+    if ((data->mod_accounting == NULL) || (data->func_accounting == NULL)){
+       data->pFunc_accounting = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_accounting);
+
+       /* Import modules */
+       data->pModule_accounting = PyImport_Import(pName);
+       if (data->pModule_accounting != NULL) {
+           PyObject *pDict;
+               
+           pDict = PyModule_GetDict(data->pModule_accounting);
+           /* pDict: borrowed reference */
+
+           data->pFunc_accounting =
+               PyDict_GetItemString(pDict, data->func_accounting);
+           /* pFunc: Borrowed reference */
+       }
+       else {
+           python_error();
+
+           /* No need to decrement references because we are exiting. */
+               
+           radlog(L_ERR, "Failed to import Python module \"%s\"\n",
+                  data->mod_accounting);
+           return -1;
+       }
+    }
+
+    if ((data->mod_checksimul == NULL) || (data->func_checksimul == NULL)){
+       data->pFunc_checksimul = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_checksimul);
+
+       /* Import modules */
+       data->pModule_checksimul = PyImport_Import(pName);
+       if (data->pModule_checksimul != NULL) {
+           PyObject *pDict;
+               
+           pDict = PyModule_GetDict(data->pModule_checksimul);
+           /* pDict: borrowed reference */
+
+           data->pFunc_checksimul =
+               PyDict_GetItemString(pDict, data->func_checksimul);
+           /* pFunc: Borrowed reference */
+       }
+       else {
+           python_error();
+               
+           /* No need to decrement references because we are exiting. */
+               
+           radlog(L_ERR, "Failed to import Python module \"%s\"\n",
+                  data->mod_checksimul);
+           return -1;
+       }
+    }
+
+
+    if ((data->mod_detach == NULL) || (data->func_detach == NULL)) {
+       data->func_detach = NULL;
+    }
+    else {
+       pName = PyString_FromString(data->mod_detach);
+
+       /* Import modules */
+       data->pModule_detach = PyImport_Import(pName);
+       if (data->pModule_detach != NULL) {
+           PyObject *pDict;
+               
+           pDict = PyModule_GetDict(data->pModule_detach);
+           /* pDict: borrowed reference */
+
+           data->pFunc_detach =
+               PyDict_GetItemString(pDict, data->func_detach);
+           /* pFunc: Borrowed reference */
+       }
+       else {
+           python_error();
+               
+           /* No need to decrement references because we are exiting. */
+               
+           radlog(L_ERR, "Failed to import Python module \"%s\"\n",
+                  data->mod_detach);
+           return -1;
+       }
+    }
+
+
+    /* Call the instantiate function.  No request.  Use the return value. */
+    return python_function(NULL, data->pFunc_instantiate, "instantiate");
+}
+
+/* Wrapper functions */
+static int python_authorize(void *instance, REQUEST *request)
+{
+    return python_function(request, 
+                          ((struct rlm_python_t *)instance)->pFunc_authorize,
+                          "authorize");
+}
+
+static int python_authenticate(void *instance, REQUEST *request)
+{
+    return python_function(
+       request, 
+       ((struct rlm_python_t *)instance)->pFunc_authenticate,
+       "authenticate");
+}
+
+static int python_preacct(void *instance, REQUEST *request)
+{
+    return python_function(
+       request, 
+       ((struct rlm_python_t *)instance)->pFunc_preacct,
+       "preacct");
+}
+
+static int python_accounting(void *instance, REQUEST *request)
+{
+    return python_function(
+       request, 
+       ((struct rlm_python_t *)instance)->pFunc_accounting,
+       "accounting");
+}
+
+static int python_checksimul(void *instance, REQUEST *request)
+{
+    return python_function(
+       request, 
+       ((struct rlm_python_t *)instance)->pFunc_checksimul,
+       "checksimul");
+}
+
 
 static int python_detach(void *instance)
 {
-       free(instance);
-       return 0;
+    int return_value;
+    
+    /* Default return value is failure */
+    return_value = -1;
+
+    if (((rlm_python_t *)instance)->pFunc_detach &&
+       PyCallable_Check(((rlm_python_t *)instance)->pFunc_detach)) {
+       
+       PyObject *pArgs, *pValue;
+       
+       /* call the function with an empty tuple */
+
+       pArgs = PyTuple_New(0);
+       pValue = PyObject_CallObject(((rlm_python_t *)instance)->pFunc_detach,
+                                    pArgs);
+       
+       if (pValue == NULL) {
+           python_error();
+           return -1;
+       }
+       else {
+           if (!PyInt_Check(pValue)) {
+               radlog(L_ERR, "detach: return value not an integer");
+           }
+           else {
+               return_value = PyInt_AsLong(pValue);
+           }
+       }
+
+       /* Decrease reference counts for the argument and return tuple */
+       Py_DECREF(pArgs);
+       Py_DECREF(pValue);
+    }
+
+    free(instance);
+
+#if 0
+    /* xxx test delete module object so it will be reloaded later.
+     * xxx useless since we can't SIGHUP reliably, anyway.
+     */
+    PyObject_Del(((struct rlm_python_t *)instance)->pModule_accounting);
+#endif
+
+    radlog(L_DBG, "python_detach done");
+    
+    /* Return the specified by the Python module */
+    return return_value;
 }
 
 /*
@@ -390,22 +930,11 @@ module_t rlm_python = {
        python_init,                    /* initialization */
        python_instantiate,             /* instantiation */
        {
-#if 0
                python_authenticate,    /* authentication */
-#else
-               NULL,
-#endif       
                python_authorize,       /* authorization */
-#if 0      
-
-               python_preacct, /* preaccounting */
+               python_preacct,         /* preaccounting */
                python_accounting,      /* accounting */
                python_checksimul       /* checksimul */
-#else
-               NULL,
-               NULL,
-               NULL,
-#endif         
        },
        python_detach,                  /* detach */
        NULL,                           /* destroy */