Polled from branch_1_1 fix for bug #348
[freeradius.git] / src / modules / rlm_perl / rlm_perl.c
index 37646e7..1b6041d 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * rlm_perl.c  
+ * rlm_perl.c
  *
- * Version:    $Id$
+ * Version:    $Id$
  *
  *   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
  *
  *   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
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
  * Copyright 2002  The FreeRADIUS server project
  * Copyright 2002  Boian Jordanov <bjordanov@orbitel.bg>
  */
 
-#include "autoconf.h"
-#include "libradius.h"
+#include <freeradius-devel/autoconf.h>
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "radiusd.h"
-#include "modules.h"
-#include "conffile.h"
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/conffile.h>
 
 #ifdef DEBUG
 #undef DEBUG
 #endif
 
+#ifdef INADDR_ANY
+#undef INADDR_ANY
+#endif
+
 #include <EXTERN.h>
 #include <perl.h>
 #include <XSUB.h>
+#include <dlfcn.h>
+#include <semaphore.h>
 
-#ifndef DO_CLEAN
-#define DO_CLEAN 0
+#ifdef __APPLE__
+extern char **environ;
 #endif
 
 static const char rcsid[] = "$Id$";
 
+#ifdef USE_ITHREADS
+
+/*
+ * Pool of Perl's clones (genetically cloned) ;)
+ *
+ */
+typedef struct pool_handle {
+       struct pool_handle      *next;
+       struct pool_handle      *prev;
+       enum {busy, idle}       status;
+       unsigned int            request_count;
+       PerlInterpreter         *clone;
+       perl_mutex              lock;
+} POOL_HANDLE;
+
+typedef struct PERL_POOL {
+       POOL_HANDLE     *head;
+       POOL_HANDLE     *tail;
+
+       int             current_clones;
+       int             active_clones;
+       int             max_clones;
+       int             start_clones;
+       int             min_spare_clones;
+       int             max_spare_clones;
+       int             max_request_per_clone;
+       int             cleanup_delay;
+       enum {yes,no}   detach;
+       perl_mutex      mutex;
+       time_t          time_when_last_added;
+} PERL_POOL;
+
+#endif
+
 /*
  *     Define a structure for our module configuration.
  *
@@ -54,15 +93,29 @@ static const char rcsid[] = "$Id$";
  *     be used as the instance handle.
  */
 typedef struct perl_inst {
-       char    *cmd;
-       char    *persistent;
-       char    *xlat_name;
+       /* Name of the perl module */
+       char    *module;
 
-       PerlInterpreter         *perl;
-       HV                      *env_hv;
-       HV                      *result_hv;
+       /* Name of the functions for each module method */
+       char    *func_authorize;
+       char    *func_authenticate;
+       char    *func_accounting;
+       char    *func_start_accounting;
+       char    *func_stop_accounting;
+       char    *func_preacct;
+       char    *func_checksimul;
+       char    *func_detach;
+       char    *func_xlat;
+       char    *func_pre_proxy;
+       char    *func_post_proxy;
+       char    *func_post_auth;
+       char    *xlat_name;
+       char    *perl_flags;
+       PerlInterpreter *perl;
+#ifdef USE_ITHREADS
+       PERL_POOL       *perl_pool;
+#endif
 } PERL_INST;
-
 /*
  *     A mapping of configuration file names to internal variables.
  *
@@ -72,134 +125,578 @@ typedef struct perl_inst {
  *     to the strdup'd string into 'config.string'.  This gets around
  *     buffer over-flows.
  */
-static CONF_PARSER module_config[] = {
-  { "cmd",  PW_TYPE_STRING_PTR, offsetof(PERL_INST,cmd), NULL,  NULL},
-  { "persistent", PW_TYPE_STRING_PTR, offsetof(PERL_INST,persistent), NULL, NULL},
-  { NULL, -1, 0, NULL, NULL }          /* end the list */
+static const CONF_PARSER module_config[] = {
+       { "module",  PW_TYPE_FILENAME,
+         offsetof(PERL_INST,module), NULL,  "module"},
+       { "func_authorize", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_authorize), NULL, "authorize"},
+       { "func_authenticate", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_authenticate), NULL, "authenticate"},
+       { "func_accounting", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_accounting), NULL, "accounting"},
+       { "func_preacct", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_preacct), NULL, "preacct"},
+       { "func_checksimul", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_checksimul), NULL, "checksimul"},
+       { "func_detach", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_detach), NULL, "detach"},
+       { "func_xlat", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_xlat), NULL, "xlat"},
+       { "func_pre_proxy", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_pre_proxy), NULL, "pre_proxy"},
+       { "func_post_proxy", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_post_proxy), NULL, "post_proxy"},
+       { "func_post_auth", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_post_auth), NULL, "post_auth"},
+       { "perl_flags", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,perl_flags), NULL, NULL},
+       { "func_start_accounting", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_start_accounting), NULL, NULL},
+       { "func_stop_accounting", PW_TYPE_STRING_PTR,
+         offsetof(PERL_INST,func_stop_accounting), NULL, NULL},
+
+       { NULL, -1, 0, NULL, NULL }             /* end the list */
 };
 
 /*
  * man perlembed
- */ 
+ */
 EXTERN_C void boot_DynaLoader(pTHX_ CV* cv);
 
+#ifdef USE_ITHREADS
 /*
- *     Do any per-module initialization.  e.g. set up connections
- *     to external databases, read configuration files, set up
- *     dictionary entries, etc.
- *
- *     Try to avoid putting too much stuff in here - it's better to
- *     do it in instantiate() where it is not global.
+ *     We use one perl to clone from it i.e. main boss
+ *     We clone it for every instance if we have perl
+ *     with -Duseithreads compiled in
  */
-static int perl_init(void)
+static PerlInterpreter *interp = NULL;
+
+static const CONF_PARSER pool_conf[] = {
+       { "max_clones", PW_TYPE_INTEGER, offsetof(PERL_POOL, max_clones), NULL,         "32"},
+       { "start_clones",PW_TYPE_INTEGER, offsetof(PERL_POOL, start_clones), NULL,              "32"},
+       { "min_spare_clones",PW_TYPE_INTEGER, offsetof(PERL_POOL, min_spare_clones),NULL,       "0"},
+       { "max_spare_clones",PW_TYPE_INTEGER, offsetof(PERL_POOL,max_spare_clones),NULL,        "32"},
+       { "cleanup_delay",PW_TYPE_INTEGER, offsetof(PERL_POOL,cleanup_delay),NULL,              "5"},
+       { "max_request_per_clone",PW_TYPE_INTEGER, offsetof(PERL_POOL,max_request_per_clone),NULL,      "0"},
+       { NULL, -1, 0, NULL, NULL }             /* end the list */
+};
+
+
+#define dl_librefs "DynaLoader::dl_librefs"
+#define dl_modules "DynaLoader::dl_modules"
+static void rlm_perl_clear_handles(pTHX)
+{
+       AV *librefs = get_av(dl_librefs, FALSE);
+       if (librefs) {
+               av_clear(librefs);
+       }
+}
+
+static void **rlm_perl_get_handles(pTHX)
+{
+       I32 i;
+       AV *librefs = get_av(dl_librefs, FALSE);
+       AV *modules = get_av(dl_modules, FALSE);
+       void **handles;
+
+       if (!librefs) {
+               radlog(L_ERR,
+                  "Could not get @%s for unloading.\n",
+                  dl_librefs);
+               return NULL;
+       }
+
+       if (!(AvFILL(librefs) >= 0)) {
+               return NULL;
+       }
+
+       handles = (void **)rad_malloc(sizeof(void *) * (AvFILL(librefs)+2));
+
+       for (i=0; i<=AvFILL(librefs); i++) {
+               void *handle;
+               SV *handle_sv = *av_fetch(librefs, i, FALSE);
+
+               if(!handle_sv) {
+                   radlog(L_ERR,
+                              "Could not fetch $%s[%d]!\n",
+                              dl_librefs, (int)i);
+                   continue;
+               }
+               handle = (void *)SvIV(handle_sv);
+
+               if (handle) {
+                   handles[i] = handle;
+               }
+       }
+
+       av_clear(modules);
+       av_clear(librefs);
+
+       handles[i] = (void *)0;
+
+       return handles;
+}
+
+static void rlm_perl_close_handles(void **handles)
+{
+       int i;
+
+       if (!handles) {
+               return;
+       }
+
+       for (i=0; handles[i]; i++) {
+               radlog(L_DBG, "close 0x%lx\n", (unsigned long)handles[i]);
+               dlclose(handles[i]);
+       }
+
+       free(handles);
+}
+
+static PerlInterpreter *rlm_perl_clone(PerlInterpreter *perl)
+{
+       PerlInterpreter *clone;
+       UV clone_flags = 0;
+
+       PERL_SET_CONTEXT(perl);
+
+       clone = perl_clone(perl, clone_flags);
+       {
+               dTHXa(clone);
+       }
+#if PERL_REVISION >= 5 && PERL_VERSION <8
+       call_pv("CLONE",0);
+#endif
+       ptr_table_free(PL_ptr_table);
+       PL_ptr_table = NULL;
+
+       PERL_SET_CONTEXT(aTHX);
+       rlm_perl_clear_handles(aTHX);
+
+       return clone;
+}
+
+static void rlm_perl_destruct(PerlInterpreter *perl)
 {
-       /*      
-        *      Everything's OK, return without an error.
+       char **orig_environ = NULL;
+       dTHXa(perl);
+
+       PERL_SET_CONTEXT(perl);
+
+       PL_perl_destruct_level = 2;
+
+       PL_origenviron = environ;
+
+       {
+               dTHXa(perl);
+       }
+       /*
+        * FIXME: This shouldn't happen
+        *
         */
-       return 0;       
+       while (PL_scopestack_ix > 1 ){
+               LEAVE;
+       }
+
+       perl_destruct(perl);
+       perl_free(perl);
+
+       if (orig_environ) {
+               environ = orig_environ;
+       }
 }
 
+static void rlm_destroy_perl(PerlInterpreter *perl)
+{
+       void    **handles;
+
+       dTHXa(perl);
+       PERL_SET_CONTEXT(perl);
+
+       handles = rlm_perl_get_handles(aTHX);
+       rlm_perl_destruct(perl);
+       rlm_perl_close_handles(handles);
+}
+
+static void delete_pool_handle(POOL_HANDLE *handle, PERL_INST *inst)
+{
+        POOL_HANDLE *prev;
+        POOL_HANDLE *next;
+
+        prev = handle->prev;
+        next = handle->next;
+
+        if (prev == NULL) {
+               inst->perl_pool->head = next;
+        } else {
+                prev->next = next;
+        }
+
+        if (next == NULL) {
+               inst->perl_pool->tail = prev;
+        } else {
+                next->prev = prev;
+        }
+       inst->perl_pool->current_clones--;
+       MUTEX_DESTROY(&handle->lock);
+       free(handle);
+}
+
+static void move2tail(POOL_HANDLE *handle, PERL_INST *inst)
+{
+       POOL_HANDLE *prev;
+       POOL_HANDLE *next;
+
+       if (inst->perl_pool->head == NULL) {
+
+               handle->prev = NULL;
+               handle->next = NULL;
+               inst->perl_pool->head = handle;
+               inst->perl_pool->tail = handle;
+               return;
+       }
+
+       if (inst->perl_pool->tail == handle) {
+               return;
+       }
+
+       prev = handle->prev;
+       next = handle->next;
+
+       if ((next != NULL) ||
+                       (prev != NULL)) {
+               if (next == NULL) {
+                       return;
+               }
+
+               if (prev == NULL) {
+                       inst->perl_pool->head = next;
+                       next->prev = NULL;
+
+               } else {
+
+                       prev->next = next;
+                       next->prev = prev;
+               }
+       }
+
+       handle->next = NULL;
+       prev = inst->perl_pool->tail;
+
+       inst->perl_pool->tail = handle;
+       handle->prev = prev;
+       prev->next = handle;
+}
+
+
+static POOL_HANDLE *pool_grow (PERL_INST *inst) {
+       POOL_HANDLE *handle;
+       time_t  now;
+
+       if (inst->perl_pool->max_clones == inst->perl_pool->current_clones) {
+               return NULL;
+       }
+       if (inst->perl_pool->detach == yes ) {
+               return NULL;
+       }
+
+       handle = (POOL_HANDLE *)rad_malloc(sizeof(POOL_HANDLE));
+
+       if (!handle) {
+               radlog(L_ERR,"Could not find free memory for pool. Aborting");
+               return NULL;
+       }
+
+       handle->prev = NULL;
+       handle->next = NULL;
+       handle->status = idle;
+       handle->clone = rlm_perl_clone(inst->perl);
+       handle->request_count = 0;
+       MUTEX_INIT(&handle->lock);
+       inst->perl_pool->current_clones++;
+       move2tail(handle, inst);
+
+       now = time(NULL);
+       inst->perl_pool->time_when_last_added = now;
+
+       return handle;
+}
+
+static POOL_HANDLE *pool_pop(PERL_INST *inst)
+{
+       POOL_HANDLE     *handle;
+       POOL_HANDLE     *found;
+       POOL_HANDLE     *tmp;
+       /*
+        * Lock the pool and be fast other thread maybe
+        * waiting for us to finish
+        */
+       MUTEX_LOCK(&inst->perl_pool->mutex);
+
+       found = NULL;
+
+       for (handle = inst->perl_pool->head; handle ; handle = tmp) {
+               tmp = handle->next;
+
+               if (handle->status == idle){
+                       found = handle;
+                       break;
+               }
+       }
+
+       if (found == NULL) {
+               if (inst->perl_pool->current_clones < inst->perl_pool->max_clones ) {
+
+                       found = pool_grow(inst);
+
+                       if (found == NULL) {
+                               radlog(L_ERR,"Cannot grow pool returning");
+                               MUTEX_UNLOCK(&inst->perl_pool->mutex);
+                               return NULL;
+                       }
+               } else {
+                       radlog(L_ERR,"rlm_perl:: reached maximum clones %d cannot grow",
+                                       inst->perl_pool->current_clones);
+                       MUTEX_UNLOCK(&inst->perl_pool->mutex);
+                       return NULL;
+               }
+       }
+
+       move2tail(found, inst);
+       found->status = busy;
+       MUTEX_LOCK(&found->lock);
+       inst->perl_pool->active_clones++;
+       found->request_count++;
+       /*
+        * Hurry Up
+        */
+       MUTEX_UNLOCK(&inst->perl_pool->mutex);
+       radlog(L_DBG,"perl_pool: item 0x%lx asigned new request. Handled so far: %d",
+                       (unsigned long) found->clone, found->request_count);
+       return found;
+}
+static int pool_release(POOL_HANDLE *handle, PERL_INST *inst) {
+
+       POOL_HANDLE *tmp, *tmp2;
+       int spare, i, t;
+       time_t  now;
+       /*
+        * Lock it
+        */
+       MUTEX_LOCK(&inst->perl_pool->mutex);
+
+       /*
+        * If detach is set then just release the mutex
+        */
+       if (inst->perl_pool->detach == yes ) {
+       handle->status = idle;
+               MUTEX_UNLOCK(&handle->lock);
+               MUTEX_UNLOCK(&inst->perl_pool->mutex);
+               return 0;
+       }
+
+       MUTEX_UNLOCK(&handle->lock);
+       handle->status = idle;
+       inst->perl_pool->active_clones--;
+
+       spare = inst->perl_pool->current_clones - inst->perl_pool->active_clones;
+
+       radlog(L_DBG,"perl_pool total/active/spare [%d/%d/%d]"
+                       , inst->perl_pool->current_clones, inst->perl_pool->active_clones, spare);
+
+       if (spare < inst->perl_pool->min_spare_clones) {
+               t = inst->perl_pool->min_spare_clones - spare;
+               for (i=0;i<t; i++) {
+                       if ((tmp = pool_grow(inst)) == NULL) {
+                               MUTEX_UNLOCK(&inst->perl_pool->mutex);
+                               return -1;
+                       }
+               }
+               MUTEX_UNLOCK(&inst->perl_pool->mutex);
+               return 0;
+       }
+       now = time(NULL);
+       if ((now - inst->perl_pool->time_when_last_added) < inst->perl_pool->cleanup_delay) {
+               MUTEX_UNLOCK(&inst->perl_pool->mutex);
+               return 0;
+       }
+       if (spare > inst->perl_pool->max_spare_clones) {
+               spare -= inst->perl_pool->max_spare_clones;
+               for (tmp = inst->perl_pool->head; (tmp !=NULL ) && (spare > 0) ; tmp = tmp2) {
+                       tmp2 = tmp->next;
+
+                       if(tmp->status == idle) {
+                               rlm_destroy_perl(tmp->clone);
+                               delete_pool_handle(tmp,inst);
+                               spare--;
+                               break;
+                       }
+               }
+       }
+       /*
+        * If the clone have reached max_request_per_clone clean it.
+        */
+       if (inst->perl_pool->max_request_per_clone > 0 ) {
+                       if (handle->request_count > inst->perl_pool->max_request_per_clone) {
+                               rlm_destroy_perl(handle->clone);
+                               delete_pool_handle(handle,inst);
+               }
+       }
+       /*
+        * Hurry Up :)
+        */
+       MUTEX_UNLOCK(&inst->perl_pool->mutex);
+       return 0;
+}
+static int init_pool (CONF_SECTION *conf, PERL_INST *inst) {
+       POOL_HANDLE     *handle;
+       int t;
+       PERL_POOL       *pool;
+
+
+       pool = rad_malloc(sizeof(PERL_POOL));
+       memset(pool,0,sizeof(PERL_POOL));
+
+       inst->perl_pool = pool;
+
+       MUTEX_INIT(&pool->mutex);
+
+       /*
+        * Read The Config
+        *
+        */
+
+       cf_section_parse(conf,pool,pool_conf);
+       inst->perl_pool = pool;
+       inst->perl_pool->detach = no;
+
+       for(t = 0;t < inst->perl_pool->start_clones ;t++){
+               if ((handle = pool_grow(inst)) == NULL) {
+                       return -1;
+               }
+
+       }
+
+       return 1;
+}
+#endif
 
-/*
- * man perlembed
- */ 
 static void xs_init(pTHX)
 {
-       const char *file = __FILE__;
-       dXSUB_SYS; 
+       char *file = __FILE__;
 
        /* DynaLoader is a special case */
-       DEBUG("rlm_perl:: xs_init enter \n");
-       newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); 
-       DEBUG("rlm_perl:: xs_init leave \n");
-       
-}
+       newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
 
+}
 /*
- * Perl xlat we use already running perl just recompile and store in memory 
- * REMEMBER: each file will only be compiled once. Note that the process 
- * will continue to grow for each file that it uses and is not compiled. 
- * 
- *  e.g.
- *  %{perl:/usr/bin/test.pl %{User-Name}} will run /usr/bin/test.pl,
- * give @ARGV with User-Name in $ARGV[0]
- *  To return something just assign it to $main::ret_val
  *
- * test.pl:
- *
- * use strict;
- * $!=0;
- * my ($ret_val);
- * print "ARGV[1]=$ARGV[1] ARGV[2]=$ARGV[2]\n";
- * $main::ret_val = "B";
- * die;
- */ 
-
-static int perl_xlat(void *instance, REQUEST *request, char *fmt, char *out, int freespace,
-                                       RADIUS_ESCAPE_STRING func)
+ * This is wrapper for radlog
+ * Now users can call radiusd::radlog(level,msg) wich is the same
+ * calling radlog from C code.
+ * Boyan
+ */
+static XS(XS_radiusd_radlog)
 {
-       PERL_INST       *inst=instance;
-       AV      *array_av;
-       SV      *ret_val;
-       char    *args[] = {"", DO_CLEAN, NULL};         
-       char    params[1024], *tmp_ptr, *ptr;
-       int     exitstatus=0,len;
-       I32     key;
-       STRLEN  n_a;
-       
-       /*
-         * Do an xlat on the provided string (nice recursive operation).
-        */
-       
-        if (!radius_xlat(params, sizeof(params), fmt, request, func))
+       dXSARGS;
+       if (items !=2)
+              croak("Usage: radiusd::radlog(level, message)");
+       {
+              int     level;
+              char    *msg;
+
+              level = (int) SvIV(ST(0));
+              msg   = (char *) SvPV(ST(1), PL_na);
+
+              /*
+               *       Because 'msg' is a 'char *', we don't want '%s', etc.
+               *       in it to give us printf-style vulnerabilities.
+               */
+              radlog(level, "rlm_perl: %s", msg);
+       }
+       XSRETURN_NO;
+}
+
+/*
+ * The xlat function
+ */
+static int perl_xlat(void *instance, REQUEST *request, char *fmt, char * out,
+                    size_t freespace, RADIUS_ESCAPE_STRING func)
+{
+
+       PERL_INST       *inst= (PERL_INST *) instance;
+       PerlInterpreter *perl;
+       char            params[1024], *ptr, *tmp;
+       int             count, ret=0;
+       STRLEN          n_a;
+#ifndef USE_ITHREADS
+       perl = inst->perl;
+#endif
+#ifdef USE_ITHREADS
+       POOL_HANDLE     *handle;
+
+       if ((handle = pool_pop(instance)) == NULL) {
+               return 0;
+       }
+
+       perl = handle->clone;
+
+       radlog(L_DBG,"Found a interpetator 0x%lx",(unsigned long) perl);
+       {
+       dTHXa(perl);
+       }
+#endif
+       PERL_SET_CONTEXT(perl);
        {
+       dSP;
+       ENTER;SAVETMPS;
+
+       /*
+        * Do an xlat on the provided string (nice recursive operation).
+       */
+       if (!radius_xlat(params, sizeof(params), fmt, request, func)) {
                radlog(L_ERR, "rlm_perl: xlat failed.");
                return 0;
-        }
-       
-       PERL_SET_CONTEXT(inst->perl);
+       }
        ptr = strtok(params, " ");
 
-       args[0] = ptr;
-       array_av = get_av("ARGV",0);
-       
-       ret_val = get_sv("main::ret_val",1);
-       
-       key = 0;
-       while ((tmp_ptr=strtok(NULL, " ")) != NULL) {
-               av_store(array_av,key++,newSVpv(tmp_ptr,0));
-       } 
-       
-       PERL_SET_CONTEXT(inst->perl);
-       call_argv("Embed::Persistent::eval_file", G_DISCARD | G_EVAL, args);
-       
-       if (SvTRUE(ERRSV)) { 
-               exitstatus = SvIV(perl_get_sv("!",FALSE));
-               radlog(L_INFO, "perl_embed::perl_xlat exit status=%d, %s\n", exitstatus, SvPV(ERRSV,n_a));
-       } 
-       
-       /*
-        * Now get the variable we need 
-        * His name is $ret_val
-        */
-       
-       out = NULL;     
-        
-       if (SvTRUE(ret_val)) { 
-               
-               out =  SvPV(ret_val,n_a);
-               len = strlen(out);
-               
-               radlog(L_INFO,"Len is %d , out is %s", len, out);
-               
-               if (len <= freespace)
-                       return len;
+       PUSHMARK(SP);
+
+       while (ptr != NULL) {
+               XPUSHs(sv_2mortal(newSVpv(ptr,0)));
+               ptr = strtok(NULL, " ");
+       }
+
+       PUTBACK;
+
+       count = call_pv(inst->func_xlat, G_SCALAR | G_EVAL);
+
+       SPAGAIN;
+       if (SvTRUE(ERRSV)) {
+               radlog(L_ERR, "rlm_perl: perl_xlat exit %s\n",
+                      SvPV(ERRSV,n_a));
+               return 0;
        }
-       
-       return 0;
-        
-}
 
+       if (count > 0) {
+               tmp = POPp;
+               ret = strlen(tmp);
+               strncpy(out,tmp,ret);
+
+               radlog(L_DBG,"rlm_perl: Len is %d , out is %s freespace is %d",
+                      ret, out,freespace);
+
+               PUTBACK ;
+               FREETMPS ;
+               LEAVE ;
+
+       }
+       }
+#ifdef USE_ITHREADS
+       pool_release(handle, instance);
+#endif
+       return ret;
+}
 /*
  *     Do any per-module initialization that is separate to each
  *     configured instance of the module.  e.g. set up connections
@@ -209,20 +706,32 @@ static int perl_xlat(void *instance, REQUEST *request, char *fmt, char *out, int
  *     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.
+ *
+ *     Boyan:
+ *     Setup a hashes wich we will use later
+ *     parse a module and give him a chance to live
+ *
  */
 static int perl_instantiate(CONF_SECTION *conf, void **instance)
 {
-       PERL_INST       *inst;  
-       char *embed[2], *xlat_name;
-       int exitstatus = 0;
-       
+       PERL_INST       *inst = (PERL_INST *) instance;
+       HV              *rad_reply_hv;
+       HV              *rad_check_hv;
+       HV              *rad_request_hv;
+       HV              *rad_request_proxy_hv;
+       HV              *rad_request_proxy_reply_hv;
+       AV              *end_AV;
+
+       char *embed[4];
+       const char *xlat_name;
+       int exitstatus = 0, argc=0;
+
        /*
         *      Set up a storage area for instance data
         */
-       
        inst = rad_malloc(sizeof(PERL_INST));
        memset(inst, 0, sizeof(PERL_INST));
-               
+
        /*
         *      If the configuration parameters can't be parsed, then
         *      fail.
@@ -231,149 +740,324 @@ static int perl_instantiate(CONF_SECTION *conf, void **instance)
                free(inst);
                return -1;
        }
-       
-       
-       /*
-        * Boyan
-        * Prepare perl instance 
-        * 
-        */ 
-       if((inst->perl = perl_alloc()) == NULL) {
-                radlog(L_INFO, "no memory!");
-               return -1;
-       }
-       
-       PERL_SET_CONTEXT(inst->perl);
-        perl_construct(inst->perl);
-       
-       PERL_SET_CONTEXT(inst->perl);
+
 
        embed[0] = NULL;
-       embed[1] = inst->persistent;
-       
-       exitstatus = perl_parse(inst->perl, xs_init, 2, embed, NULL);
+       if (inst->perl_flags) {
+               embed[1] = inst->perl_flags;
+               embed[2] = inst->module;
+               embed[3] = "0";
+               argc = 4;
+       } else {
+               embed[1] = inst->module;
+               embed[2] = "0";
+               argc = 3;
+       }
 
+#ifdef USE_ITHREADS
+       if (!interp) {
+               if ((interp = perl_alloc()) == NULL) {
+                       radlog(L_DBG, "rlm_perl: No memory for allocating new perl !");
+                       return -1;
+               }
+               
+               perl_construct(interp);
+               PL_perl_destruct_level = 2;
+       }
+
+       inst->perl = interp;
+       {
+       dTHXa(inst->perl);
+       }
        PERL_SET_CONTEXT(inst->perl);
+#else
+       if ((inst->perl = perl_alloc()) == NULL) {
+               radlog(L_ERR, "rlm_perl: No memory for allocating new perl !");
+               return -1;
+       }
+
+       perl_construct(inst->perl);
+#endif
+
+#if PERL_REVISION >= 5 && PERL_VERSION >=8
+       PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+#endif
+
+       exitstatus = perl_parse(inst->perl, xs_init, argc, embed, NULL);
+
+       end_AV = PL_endav;
+       PL_endav = Nullav;
+
        if(!exitstatus) {
                exitstatus = perl_run(inst->perl);
        } else {
-               radlog(L_INFO,"perl_parse failed: %s not found or has syntax errors. \n", inst->persistent);
+               radlog(L_ERR,"rlm_perl: perl_parse failed: %s not found or has syntax errors. \n", inst->module);
                return (-1);
        }
 
-       inst->env_hv = perl_get_hv("ENV",0);
-        inst->result_hv = perl_get_hv("main::result",1);
-       
+       PL_endav = end_AV;
+
+        newXS("radiusd::radlog",XS_radiusd_radlog, "rlm_perl.c");
+
+       rad_reply_hv = newHV();
+       rad_check_hv = newHV();
+       rad_request_hv = newHV();
+       rad_request_proxy_hv = newHV();
+       rad_request_proxy_reply_hv = newHV();
+
+       rad_reply_hv = get_hv("RAD_REPLY",1);
+        rad_check_hv = get_hv("RAD_CHECK",1);
+        rad_request_hv = get_hv("RAD_REQUEST",1);
+       rad_request_proxy_hv = get_hv("RAD_REQUEST_PROXY",1);
+       rad_request_proxy_reply_hv = get_hv("RAD_REQUEST_PROXY_REPLY",1);
+
        xlat_name = cf_section_name2(conf);
-       if (xlat_name == NULL) 
+       if (xlat_name == NULL)
                xlat_name = cf_section_name1(conf);
-       if (xlat_name){ 
+       if (xlat_name){
                inst->xlat_name = strdup(xlat_name);
-               xlat_register(xlat_name, perl_xlat, inst); 
-       } 
+               xlat_register(xlat_name, perl_xlat, inst);
+       }
+
+#ifdef USE_ITHREADS
+       if ((init_pool(conf, inst)) == -1) {
+               radlog(L_ERR,"Couldn't init a pool of perl clones. Exiting");
+               return -1;
+       }
+
+#endif
        *instance = inst;
-       
+
        return 0;
 }
 
 /*
- *  Boyan get the request and put them in perl hash 
- *  which will be given to perl cmd
+ *     get the vps and put them in perl hash
+ *     If one VP have multiple values it is added as array_ref
+ *     Example for this is Cisco-AVPair that holds multiple values.
+ *     Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'}
  */
-static void perl_env(VALUE_PAIR *vp, PERL_INST *inst)
+static void perl_store_vps(VALUE_PAIR *vp, HV *rad_hv)
 {
-        char            buffer[256];
+        VALUE_PAIR     *nvp, *vpa, *vpn;
+       AV              *av;
+       char            buffer[1024];
+       int             attr, len;
 
-        hv_clear(inst->env_hv);
-        hv_clear(inst->result_hv);
+       hv_undef(rad_hv);
+       nvp = paircopy(vp);
 
-       for ( ; vp != NULL; vp = vp->next) {
-               int len;
+       while (nvp != NULL) {
+               attr = nvp->attribute;
+               vpa = paircopy2(nvp,attr);
+               if (vpa->next) {
+                       av = newAV();
+                       vpn = vpa;
+                       while (vpn) {
+                               len = vp_prints_value(buffer, sizeof(buffer),
+                                               vpn, FALSE);
+                               av_push(av, newSVpv(buffer, len));
+                               vpn = vpn->next;
+                       }
+                       hv_store(rad_hv, nvp->name, strlen(nvp->name),
+                                       newRV_noinc((SV *) av), 0);
+               } else {
+                       len = vp_prints_value(buffer, sizeof(buffer),
+                                       vpa, FALSE);
+                       hv_store(rad_hv, vpa->name, strlen(vpa->name),
+                                       newSVpv(buffer, len), 0);
+               }
 
-               len = vp_prints_value(buffer, sizeof(buffer), vp, FALSE);
-
-               hv_store(inst->env_hv, vp->name, strlen(vp->name),
-                        newSVpv(buffer, len),0);
+               pairfree(&vpa);
+               vpa = nvp; while ((vpa != NULL) && (vpa->attribute == attr))
+                       vpa = vpa->next;
+               pairdelete(&nvp, attr);
+               nvp = vpa;
        }
 }
 
 /*
- * return structs and status 0 OK 1 Not
- * Boyan
+ *
+ *     Verify that a Perl SV is a string and save it in FreeRadius
+ *     Value Pair Format
+ *
  */
-static int rlmperl_call(void *instance, REQUEST *request)
+static int pairadd_sv(VALUE_PAIR **vp, char *key, SV *sv) {
+       char            *val;
+       VALUE_PAIR      *vpp;
+
+       if (SvOK(sv)) {
+               val = SvPV_nolen(sv);
+               vpp = pairmake(key, val, T_OP_EQ);
+               if (vpp != NULL) {
+                       pairadd(vp, vpp);
+                       radlog(L_DBG,
+                         "rlm_perl: Added pair %s = %s", key, val);
+                      return 1;
+               } else {
+                       radlog(L_DBG,
+                         "rlm_perl: ERROR: Failed to create pair %s = %s",
+                         key, val);
+               }
+        }
+       return 0;
+}
+
+/*
+  *     Boyan :
+  *     Gets the content from hashes
+  */
+static int get_hv_content(HV *my_hv, VALUE_PAIR **vp)
 {
-               
-       PERL_INST       *inst = (PERL_INST *) instance;
-       SV              *res_sv;
+       SV              *res_sv, **av_sv;
+       AV              *av;
+       char            *key;
+       I32             key_len, len, i, j;
+       int             ret=0;
+
+       for (i = hv_iterinit(my_hv); i > 0; i--) {
+               res_sv = hv_iternextsv(my_hv,&key,&key_len);
+               if (SvROK(res_sv) && (SvTYPE(SvRV(res_sv)) == SVt_PVAV)) {
+                       av = (AV*)SvRV(res_sv);
+                       len = av_len(av);
+                       for (j = 0; j <= len; j++) {
+                               av_sv = av_fetch(av, j, 0);
+                               ret = pairadd_sv(vp, key, *av_sv) + ret;
+                       }
+               } else ret = pairadd_sv(vp, key, res_sv) + ret;
+        }
+
+        return ret;
+}
+
+/*
+ *     Call the function_name inside the module
+ *     Store all vps in hashes %RAD_CHECK %RAD_REPLY %RAD_REQUEST
+ *
+ */
+static int rlmperl_call(void *instance, REQUEST *request, char *function_name)
+{
+
+       PERL_INST       *inst = instance;
        VALUE_PAIR      *vp;
-       char            *key, *val, *ptr, *p;
-       char            *args[] = {NULL, DO_CLEAN, NULL};
-       char            answer[4096];
-       I32             key_len,i;
-       int             val_len;
-       int             exitstatus = 0, comma = 0;
-       STRLEN n_a;
-
-       args[0] = inst->cmd;
-       
-       perl_env(request->packet->vps, inst);
-       
-       for (i = hv_iterinit(inst->env_hv); i > 0; i--) {
-               res_sv = hv_iternextsv(inst->env_hv, &key, &key_len);
-               val = SvPV(res_sv,val_len);
-               radlog(L_DBG, "ENV %s= %s", key, val); 
-       }
-       
-       PERL_SET_CONTEXT(inst->perl);
-       call_argv("Embed::Persistent::eval_file", G_DISCARD | G_EVAL, args);
-       
-       exitstatus = 0;
+       int             exitstatus=0, count;
+       STRLEN          n_a;
 
-       if (SvTRUE(ERRSV)) {
-               exitstatus = SvIV(perl_get_sv("!",FALSE));;
-               radlog(L_INFO, "exit status=%d, %s\n", exitstatus,
-                      SvPV(ERRSV,n_a));
+       HV              *rad_reply_hv;
+       HV              *rad_check_hv;
+       HV              *rad_request_hv;
+       HV              *rad_request_proxy_hv;
+       HV              *rad_request_proxy_reply_hv;
+
+#ifdef USE_ITHREADS
+       POOL_HANDLE     *handle;
+
+       if ((handle = pool_pop(instance)) == NULL) {
+               return RLM_MODULE_FAIL;
        }
 
-       ptr = answer;
+       radlog(L_DBG,"found interpetator at address 0x%lx",(unsigned long) handle->clone);
+       {
+       dTHXa(handle->clone);
+       PERL_SET_CONTEXT(handle->clone);
+       }
+#else
        PERL_SET_CONTEXT(inst->perl);
-       
-       for (i = hv_iterinit(inst->result_hv); i > 0; i--) {
-               res_sv = hv_iternextsv(inst->result_hv,&key,&key_len);
-               val = SvPV(res_sv,val_len);
-               sprintf(ptr, "%s=\"%s\"\n", key, val); /* FIXME: snprintf */
-               ptr += key_len + val_len + 4;
+       radlog(L_DBG,"Using perl at 0x%lx",(unsigned long) inst->perl);
+#endif
+       {
+       dSP;
+
+       ENTER;
+       SAVETMPS;
+
+
+       /*
+        *      Radius has told us to call this function, but none
+        *      is defined.
+        */
+       if (!function_name) {
+               return RLM_MODULE_FAIL;
        }
-        /* perl_free(inst->perl); */
 
-       *ptr='\0';
-       vp = NULL;
+       rad_reply_hv = get_hv("RAD_REPLY",1);
+       rad_check_hv = get_hv("RAD_CHECK",1);
+       rad_request_hv = get_hv("RAD_REQUEST",1);
+       rad_request_proxy_hv = get_hv("RAD_REQUEST_PROXY",1);
+       rad_request_proxy_reply_hv = get_hv("RAD_REQUEST_PROXY_REPLY",1);
+
+
+       perl_store_vps(request->reply->vps, rad_reply_hv);
+       perl_store_vps(request->config_items, rad_check_hv);
+       perl_store_vps(request->packet->vps, rad_request_hv);
        
-        for (p = answer; *p; p++) { 
-               if (*p == '\n') {
-                       *p = comma ? ' ' : ',';
-                       p++; comma = 0;
-               } 
-               if (*p == ',') comma++; 
+       if (request->proxy != NULL) {
+               perl_store_vps(request->proxy->vps, rad_request_proxy_hv);
+       } else {
+               hv_undef(rad_request_proxy_hv);
        }
+
+       if (request->proxy_reply !=NULL) {
+               perl_store_vps(request->proxy_reply->vps, rad_request_proxy_reply_hv);
+       } else {
+               hv_undef(rad_request_proxy_reply_hv);
+       }       
        
+       vp = NULL;
+
+
+       PUSHMARK(SP);
        /*
-        * Replace any trailing comma by a NUL.  
-        */                                
-       if (answer[strlen(answer) - 1] == ',') {
-               answer[strlen(answer) - 1] = '\0';
+       * This way %RAD_xx can be pushed onto stack as sub parameters.
+       * XPUSHs( newRV_noinc((SV *)rad_request_hv) );
+       * XPUSHs( newRV_noinc((SV *)rad_reply_hv) );
+       * XPUSHs( newRV_noinc((SV *)rad_check_hv) );
+       * PUTBACK;
+       */
+
+       count = call_pv(function_name, G_SCALAR | G_EVAL | G_NOARGS);
+
+       SPAGAIN;
+
+       if (count == 1) {
+               exitstatus = POPi;
+               if (exitstatus >= 100 || exitstatus < 0) {
+                       exitstatus = RLM_MODULE_FAIL;
+               }
        }
-       radlog(L_INFO,"perl_embed :: value-pairs: %s", answer);
 
-       if (userparse(answer, &vp) < 0) {
-               radlog(L_ERR, "perl_embed :: %s: unparsable reply", args[0]); 
-       } else {
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+
+       if (SvTRUE(ERRSV)) {
+               radlog(L_ERR, "rlm_perl: perl_embed:: module = %s , func = %s exit status= %s\n",
+                      inst->module,
+                      function_name, SvPV(ERRSV,n_a));
+       }
+
+       if ((get_hv_content(rad_reply_hv, &vp)) > 0 ) {
                pairmove(&request->reply->vps, &vp);
                pairfree(&vp);
-       } 
-       return exitstatus;
+       }
 
+       if ((get_hv_content(rad_check_hv, &vp)) > 0 ) {
+               pairmove(&request->config_items, &vp);
+               pairfree(&vp);
+       }
+       
+       if ((get_hv_content(rad_request_proxy_reply_hv, &vp)) > 0 && request->proxy_reply != NULL) {
+               pairfree(&request->proxy_reply->vps);
+               pairmove(&request->proxy_reply->vps, &vp);
+               pairfree(&vp);
+       }
+       }
+#ifdef USE_ITHREADS
+       pool_release(handle,instance);
+       radlog(L_DBG,"Unreserve perl at address 0x%lx", (unsigned long) handle->clone);
+#endif
+
+       return exitstatus;
 }
 
 /*
@@ -384,15 +1068,8 @@ static int rlmperl_call(void *instance, REQUEST *request)
  */
 static int perl_authorize(void *instance, REQUEST *request)
 {
-       int status = 0;
-       
-       radlog(L_INFO,"perl_embed :: Enter Authorize");
-
-       if ((status = rlmperl_call(instance, request)) == 0) {
-               return RLM_MODULE_OK;
-       }
-       
-       return RLM_MODULE_FAIL;
+       return rlmperl_call(instance, request,
+                           ((PERL_INST *)instance)->func_authorize);
 }
 
 /*
@@ -400,76 +1077,209 @@ static int perl_authorize(void *instance, REQUEST *request)
  */
 static int perl_authenticate(void *instance, REQUEST *request)
 {
-       int status = 0;
-
-       radlog(L_INFO,"perl_embed :: Enter Auth");
-
-       if ((status = rlmperl_call(instance, request)) == 0) {
-               return RLM_MODULE_OK;
-       }
-
-       return RLM_MODULE_FAIL;
+       return rlmperl_call(instance, request,
+                           ((PERL_INST *)instance)->func_authenticate);
 }
-
-
 /*
  *     Massage the request before recording it or proxying it
  */
 static int perl_preacct(void *instance, REQUEST *request)
 {
-       int status = 0;
-
-       radlog(L_INFO,"mod_perl ::  Enter PreAccounting");
-       
-       if ((status = rlmperl_call(instance, request)) == 0) {
-               return RLM_MODULE_OK;
-       }
-
-       return RLM_MODULE_FAIL;
+       return rlmperl_call(instance, request,
+                           ((PERL_INST *)instance)->func_preacct);
 }
-
 /*
  *     Write accounting information to this modules database.
  */
-
 static int perl_accounting(void *instance, REQUEST *request)
 {
-       int status = 0;
+       VALUE_PAIR      *pair;
+       int             acctstatustype=0;
 
-       radlog(L_INFO,"mod_perl ::  Enter Accounting");
-       
-       if ((status = (rlmperl_call(instance, request))) == 0) {
-               return RLM_MODULE_OK;
-       }
+       if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) {
+                acctstatustype = pair->lvalue;
+        } else {
+                radlog(L_ERR, "Invalid Accounting Packet");
+                return RLM_MODULE_INVALID;
+        }
 
-       return RLM_MODULE_FAIL;
-}
+       switch (acctstatustype) {
+
+               case PW_STATUS_START:
 
+                       if (((PERL_INST *)instance)->func_start_accounting) {
+                               return rlmperl_call(instance, request,
+                                           ((PERL_INST *)instance)->func_start_accounting);
+                       } else {
+                               return rlmperl_call(instance, request,
+                                           ((PERL_INST *)instance)->func_accounting);
+                       }
+                       break;
+
+               case PW_STATUS_STOP:
+
+                       if (((PERL_INST *)instance)->func_stop_accounting) {
+                               return rlmperl_call(instance, request,
+                                           ((PERL_INST *)instance)->func_stop_accounting);
+                       } else {
+                               return rlmperl_call(instance, request,
+                                           ((PERL_INST *)instance)->func_accounting);
+                       }
+                       break;
+               default:
+                       return rlmperl_call(instance, request,
+                                           ((PERL_INST *)instance)->func_accounting);
+
+       }
+}
+/*
+ *     Check for simultaneouse-use
+ */
+static int perl_checksimul(void *instance, REQUEST *request)
+{
+       return rlmperl_call(instance, request,
+                       ((PERL_INST *)instance)->func_checksimul);
+}
+/*
+ *     Pre-Proxy request
+ */
+static int perl_pre_proxy(void *instance, REQUEST *request)
+{
+       return rlmperl_call(instance, request,
+                       ((PERL_INST *)instance)->func_pre_proxy);
+}
 /*
- * Detach a instance free all ..
+ *     Post-Proxy request
+ */
+static int perl_post_proxy(void *instance, REQUEST *request)
+{
+       return rlmperl_call(instance, request,
+                       ((PERL_INST *)instance)->func_post_proxy);
+}
+/*
+ *     Pre-Auth request
+ */
+static int perl_post_auth(void *instance, REQUEST *request)
+{
+       return rlmperl_call(instance, request,
+                       ((PERL_INST *)instance)->func_post_auth);
+}
+/*
+ * Detach a instance give a chance to a module to make some internal setup ...
  */
 static int perl_detach(void *instance)
 {
-       PERL_INST *inst=instance;
+       PERL_INST       *inst = (PERL_INST *) instance;
+       int             exitstatus = 0, count = 0;
+
+#ifdef USE_ITHREADS
+       POOL_HANDLE     *handle, *tmp, *tmp2;
+
+       MUTEX_LOCK(&inst->perl_pool->mutex);
+       inst->perl_pool->detach = yes;
+       MUTEX_UNLOCK(&inst->perl_pool->mutex);
+
+       for (handle = inst->perl_pool->head; handle != NULL; handle = handle->next) {
+
+               radlog(L_DBG,"Detach perl 0x%lx", (unsigned long) handle->clone);
+               /*
+                * Wait until clone becomes idle
+                */
+               MUTEX_LOCK(&handle->lock);
 
+               /*
+                * Give a clones chance to run detach function
+                */
+               {
+               dTHXa(handle->clone);
+               PERL_SET_CONTEXT(handle->clone);
+               {
+               dSP; ENTER; SAVETMPS; PUSHMARK(SP);
+               count = call_pv(inst->func_detach, G_SCALAR | G_EVAL );
+               SPAGAIN;
+
+               if (count == 1) {
+                       exitstatus = POPi;
+                       /*
+                        * FIXME: bug in perl
+                        *
+                        */
+                       if (exitstatus >= 100 || exitstatus < 0) {
+                               exitstatus = RLM_MODULE_FAIL;
+                       }
+               }
+               PUTBACK;
+               FREETMPS;
+               LEAVE;
+               radlog(L_DBG,"detach at 0x%lx returned status %d",
+                               (unsigned long) handle->clone, exitstatus);
+               }
+               }
+               MUTEX_UNLOCK(&handle->lock);
+       }
+       /*
+        * Free handles
+        */
+
+       for (tmp = inst->perl_pool->head; tmp !=NULL  ; tmp = tmp2) {
+               tmp2 = tmp->next;
+               radlog(L_DBG,"rlm_perl:: Destroy perl");
+               rlm_perl_destruct(tmp->clone);
+               delete_pool_handle(tmp,inst);
+       }
+
+       {
+       dTHXa(inst->perl);
+#endif /* USE_ITHREADS */
        PERL_SET_CONTEXT(inst->perl);
-       perl_destruct(inst->perl);
-        PERL_SET_CONTEXT(inst->perl);
-       perl_free(inst->perl);
+       {
+       dSP; ENTER; SAVETMPS;
+       PUSHMARK(SP);
+
+       count = call_pv(inst->func_detach, G_SCALAR | G_EVAL );
+       SPAGAIN;
 
-       hv_clear(inst->env_hv);
-       hv_clear(inst->result_hv);
+       if (count == 1) {
+               exitstatus = POPi;
+               if (exitstatus >= 100 || exitstatus < 0) {
+                       exitstatus = RLM_MODULE_FAIL;
+               }
+       }
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+       }
+#ifdef USE_ITHREADS
+       }
+#endif
 
        xlat_unregister(inst->xlat_name, perl_xlat);
        free(inst->xlat_name);
 
-       free(inst->cmd);
-       free(inst->persistent);
+       if (inst->func_authorize) free(inst->func_authorize);
+       if (inst->func_authenticate) free(inst->func_authenticate);
+       if (inst->func_accounting) free(inst->func_accounting);
+       if (inst->func_preacct) free(inst->func_preacct);
+       if (inst->func_checksimul) free(inst->func_checksimul);
+       if (inst->func_pre_proxy) free(inst->func_pre_proxy);
+       if (inst->func_post_proxy) free(inst->func_post_proxy);
+       if (inst->func_post_auth) free(inst->func_post_auth);
+       if (inst->func_detach) free(inst->func_detach);
+
+#ifdef USE_ITHREADS
+       free(inst->perl_pool->head);
+       free(inst->perl_pool->tail);
+       MUTEX_DESTROY(&inst->perl_pool->mutex);
+       free(inst->perl_pool);
+       rlm_perl_destruct(inst->perl);
+#else
+       perl_destruct(inst->perl);
+       perl_free(inst->perl);
+#endif
 
        free(inst);
-       return 0;
+       return exitstatus;
 }
-
 /*
  *     The module name should be the only globally exported symbol.
  *     That is, everything else should be 'static'.
@@ -480,20 +1290,23 @@ static int perl_detach(void *instance)
  *     is single-threaded.
  */
 module_t rlm_perl = {
+       RLM_MODULE_INIT,
        "perl",                         /* Name */
-       RLM_TYPE_THREAD_UNSAFE,         /* type */
-       perl_init,                      /* initialization */
+#ifdef USE_ITHREADS
+       RLM_TYPE_THREAD_SAFE,           /* type */
+#else
+       RLM_TYPE_THREAD_UNSAFE,
+#endif
        perl_instantiate,               /* instantiation */
+       perl_detach,                    /* detach */
        {
-               perl_authenticate,
-               perl_authorize,
-               perl_preacct,
-               perl_accounting,
-               NULL,
-               NULL,                   /* pre-proxy */
-               NULL,                   /* post-proxy */
-               NULL                    /* post-auth */
+               perl_authenticate,      /* authenticate */
+               perl_authorize,         /* authorize */
+               perl_preacct,           /* preacct */
+               perl_accounting,        /* accounting */
+               perl_checksimul,        /* check simul */
+               perl_pre_proxy,         /* pre-proxy */
+               perl_post_proxy,        /* post-proxy */
+               perl_post_auth          /* post-auth */
        },
-       perl_detach,                    /* detach */
-       NULL,                           /* destroy */
 };