document rlm_otp fd leak fix
[freeradius.git] / src / modules / rlm_python / rlm_python.c
1 /*
2  * rlm_python.c
3  *
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation; either version 2 of the License, or
8  *   (at your option) any later version.
9  *
10  *   This program is distributed in the hope that it will be useful,
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *   GNU General Public License for more details.
14  *
15  *   You should have received a copy of the GNU General Public License
16  *   along with this program; if not, write to the Free Software
17  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  *
19  * Copyright 2000  The FreeRADIUS server project
20  * Copyright 2002  Miguel A.L. Paraz <mparaz@mparaz.com>
21  * Copyright 2002  Imperium Technology, Inc.
22  */
23
24 #include <Python.h>
25
26 #include <freeradius-devel/autoconf.h>
27
28 #include <stdio.h>
29 #include <stdlib.h>
30
31 #include <freeradius-devel/radiusd.h>
32 #include <freeradius-devel/modules.h>
33 #include <freeradius-devel/conffile.h>
34
35 static const char rcsid[] = "$Id$";
36
37 /*
38  *      Define a structure for our module configuration.
39  *
40  *      These variables do not need to be in a structure, but it's
41  *      a lot cleaner to do so, and a pointer to the structure can
42  *      be used as the instance handle.
43  */
44 typedef struct rlm_python_t {
45     /* Config section */
46
47     /* Names of modules */
48     char
49         *mod_instantiate,
50         *mod_authorize,
51         *mod_authenticate,
52         *mod_preacct,
53         *mod_accounting,
54         *mod_checksimul,
55         *mod_detach,
56
57     /* Names of functions */
58         *func_instantiate,
59         *func_authorize,
60         *func_authenticate,
61         *func_preacct,
62         *func_accounting,
63         *func_checksimul,
64         *func_detach;
65
66
67     /* End Config section */
68
69
70     /* Python objects for modules */
71     PyObject
72         *pModule_builtin,
73         *pModule_instantiate,
74         *pModule_authorize,
75         *pModule_authenticate,
76         *pModule_preacct,
77         *pModule_accounting,
78         *pModule_checksimul,
79         *pModule_detach,
80
81
82         /* Functions */
83
84         *pFunc_instantiate,
85         *pFunc_authorize,
86         *pFunc_authenticate,
87         *pFunc_preacct,
88         *pFunc_accounting,
89         *pFunc_checksimul,
90         *pFunc_detach;
91
92 } rlm_python_t;
93
94 /*
95  *      A mapping of configuration file names to internal variables.
96  *
97  *      Note that the string is dynamically allocated, so it MUST
98  *      be freed.  When the configuration file parse re-reads the string,
99  *      it free's the old one, and strdup's the new one, placing the pointer
100  *      to the strdup'd string into 'config.string'.  This gets around
101  *      buffer over-flows.
102  */
103 static const CONF_PARSER module_config[] = {
104   { "mod_instantiate",  PW_TYPE_STRING_PTR,
105     offsetof(rlm_python_t, mod_instantiate), NULL,  NULL},
106   { "func_instantiate",  PW_TYPE_STRING_PTR,
107     offsetof(rlm_python_t, func_instantiate), NULL,  NULL},
108
109   { "mod_authorize",  PW_TYPE_STRING_PTR,
110     offsetof(rlm_python_t, mod_authorize), NULL,  NULL},
111   { "func_authorize",  PW_TYPE_STRING_PTR,
112     offsetof(rlm_python_t, func_authorize), NULL,  NULL},
113
114   { "mod_authenticate",  PW_TYPE_STRING_PTR,
115     offsetof(rlm_python_t, mod_authenticate), NULL,  NULL},
116   { "func_authenticate",  PW_TYPE_STRING_PTR,
117     offsetof(rlm_python_t, func_authenticate), NULL,  NULL},
118
119   { "mod_preacct",  PW_TYPE_STRING_PTR,
120     offsetof(rlm_python_t, mod_preacct), NULL,  NULL},
121   { "func_preacct",  PW_TYPE_STRING_PTR,
122     offsetof(rlm_python_t, func_preacct), NULL,  NULL},
123
124   { "mod_accounting",  PW_TYPE_STRING_PTR,
125     offsetof(rlm_python_t, mod_accounting), NULL,  NULL},
126   { "func_accounting",  PW_TYPE_STRING_PTR,
127     offsetof(rlm_python_t, func_accounting), NULL,  NULL},
128
129   { "mod_checksimul",  PW_TYPE_STRING_PTR,
130     offsetof(rlm_python_t, mod_checksimul), NULL,  NULL},
131   { "func_checksimul",  PW_TYPE_STRING_PTR,
132     offsetof(rlm_python_t, func_checksimul), NULL,  NULL},
133
134   { "mod_detach",  PW_TYPE_STRING_PTR,
135     offsetof(rlm_python_t, mod_detach), NULL,  NULL},
136   { "func_detach",  PW_TYPE_STRING_PTR,
137     offsetof(rlm_python_t, func_detach), NULL,  NULL},
138
139
140   { NULL, -1, 0, NULL, NULL }           /* end the list */
141 };
142
143 /*
144  * radiusd Python functions
145  */
146
147 /* radlog wrapper */
148 static PyObject *radlog_py(const PyObject *self, PyObject *args) {
149     int status;
150     char *msg;
151
152     if (!PyArg_ParseTuple(args, "is", &status, &msg)) {
153         return NULL;
154     }
155
156     radlog(status, msg);
157     return Py_None;
158 }
159
160 static PyMethodDef radiusd_methods[] = {
161     {"radlog", (PyCFunction)radlog_py, METH_VARARGS, "freeradius radlog()."},
162     {NULL, NULL, 0, NULL}
163 };
164
165
166 /* Extract string representation of Python error. */
167 static void python_error(void) {
168     PyObject *pType, *pValue, *pTraceback, *pStr1, *pStr2;
169
170     PyErr_Fetch(&pType, &pValue, &pTraceback);
171     pStr1 = PyObject_Str(pType);
172     pStr2 = PyObject_Str(pValue);
173
174     radlog(L_ERR, "%s: %s\n",
175            PyString_AsString(pStr1), PyString_AsString(pStr2));
176 }
177
178 /* Tuple to value pair conversion */
179 static void add_vp_tuple(VALUE_PAIR **vpp, PyObject *pValue,
180                          const char *function_name) {
181     int i, outertuplesize;
182     VALUE_PAIR  *vp;
183
184     /* If the Python function gave us None for the tuple, then just return. */
185     if (pValue == Py_None) {
186         return;
187     }
188
189     if (!PyTuple_Check(pValue)) {
190         radlog(L_ERR, "%s: non-tuple passed", function_name);
191     }
192
193     /* Get the tuple size. */
194     outertuplesize = PyTuple_Size(pValue);
195
196     for (i = 0; i < outertuplesize; i++) {
197         PyObject *pTupleElement = PyTuple_GetItem(pValue, i);
198
199         if ((pTupleElement != NULL) &&
200             (PyTuple_Check(pTupleElement))) {
201
202             /* Check if it's a pair */
203             int tuplesize;
204
205             if ((tuplesize = PyTuple_Size(pTupleElement)) != 2) {
206                 radlog(L_ERR, "%s: tuple element %d is a tuple "
207                        " of size %d. must be 2\n", function_name,
208                        i, tuplesize);
209             }
210             else {
211                 PyObject *pString1, *pString2;
212
213                 pString1 = PyTuple_GetItem(pTupleElement, 0);
214                 pString2 = PyTuple_GetItem(pTupleElement, 1);
215
216                 /* xxx PyString_Check does not compile here */
217                 if  ((pString1 != NULL) &&
218                      (pString2 != NULL) &&
219                      PyObject_TypeCheck(pString1,&PyString_Type) &&
220                      PyObject_TypeCheck(pString2,&PyString_Type)) {
221
222
223                     const char *s1, *s2;
224
225                     /* pairmake() will convert and find any
226                      * errors in the pair.
227                      */
228
229                     s1 = PyString_AsString(pString1);
230                     s2 = PyString_AsString(pString2);
231
232                     if ((s1 != NULL) && (s2 != NULL)) {
233                         radlog(L_DBG, "%s: %s = %s ",
234                                function_name, s1, s2);
235
236                         /* xxx Might need to support other T_OP */
237                         vp = pairmake(s1, s2, T_OP_EQ);
238                         if (vp != NULL) {
239                             pairadd(vpp, vp);
240                             radlog(L_DBG, "%s: s1, s2 OK\n",
241                                    function_name);
242                         }
243                         else {
244                             radlog(L_DBG, "%s: s1, s2 FAILED\n",
245                                    function_name);
246                         }
247                     }
248                     else {
249                         radlog(L_ERR, "%s: string conv failed\n",
250                                function_name);
251                     }
252
253                 }
254                 else {
255                     radlog(L_ERR, "%s: tuple element %d must be "
256                            "(string, string)", function_name, i);
257                 }
258             }
259         }
260         else {
261             radlog(L_ERR, "%s: tuple element %d is not a tuple\n",
262                    function_name, i);
263         }
264     }
265
266 }
267
268 /* This is the core Python function that the others wrap around.
269  * Pass the value-pair print strings in a tuple.
270  * xxx We're not checking the errors. If we have errors, what do we do?
271  */
272
273 static int python_function(REQUEST *request,
274                            PyObject *pFunc, const char *function_name)
275 {
276 #define BUF_SIZE 1024
277
278     char buf[BUF_SIZE];         /* same size as vp_print buffer */
279
280     VALUE_PAIR  *vp;
281
282     PyObject *pValue, *pValuePairContainer, **pValueHolder, **pValueHolderPtr;
283     int i, n_tuple, return_value;
284
285     /* Return with "OK, continue" if the function is not defined. */
286     if (pFunc == NULL) {
287         return RLM_MODULE_OK;
288     }
289
290     /* Default return value is "OK, continue" */
291     return_value = RLM_MODULE_OK;
292
293     /* We will pass a tuple containing (name, value) tuples
294      * We can safely use the Python function to build up a tuple,
295      * since the tuple is not used elsewhere.
296      *
297      * Determine the size of our tuple by walking through the packet.
298      * If request is NULL, pass None.
299      */
300     n_tuple = 0;
301
302     if (request != NULL) {
303         for (vp = request->packet->vps; vp; vp = vp->next) {
304             n_tuple++;
305         }
306     }
307
308     /* Create the tuple and a holder for the pointers, so that we can
309      * decref more efficiently later without the overhead of reading
310      * the tuple.
311      *
312      * We use malloc() instead of the Python memory allocator since we
313      * are not embedded.
314      */
315
316     if (NULL == (pValueHolder = pValueHolderPtr =
317                  malloc(sizeof(PyObject *) * n_tuple))) {
318
319         radlog(L_ERR, "%s: malloc of %d bytes failed\n",
320                function_name, sizeof(PyObject *) * n_tuple);
321
322         return -1;
323     }
324
325     if (n_tuple == 0) {
326         pValuePairContainer = Py_None;
327     }
328     else {
329         pValuePairContainer = PyTuple_New(n_tuple);
330
331         i = 0;
332         for (vp = request->packet->vps; vp; vp = vp->next) {
333             PyObject *pValuePair, *pString1, *pString2;
334
335             /* The inside tuple has two only: */
336             pValuePair = PyTuple_New(2);
337
338             /* The name. logic from vp_prints, lib/print.c */
339             if (vp->flags.has_tag) {
340                 snprintf(buf, BUF_SIZE, "%s:%d", vp->name, vp->flags.tag);
341             }
342             else {
343                 strcpy(buf, vp->name);
344             }
345
346             pString1 = PyString_FromString(buf);
347             PyTuple_SetItem(pValuePair, 0, pString1);
348
349
350             /* The value. Use delimiter - don't know what that means */
351             vp_prints_value(buf, sizeof(buf), vp, 1);
352             pString2 = PyString_FromString(buf);
353             PyTuple_SetItem(pValuePair, 1, pString2);
354
355             /* Put the tuple inside the container */
356             PyTuple_SetItem(pValuePairContainer, i++, pValuePair);
357
358             /* Store the pointer in our malloc() storage */
359             *pValueHolderPtr++ = pValuePair;
360         }
361     }
362
363
364     /* Call Python function.
365      */
366
367     if (pFunc && PyCallable_Check(pFunc)) {
368         PyObject *pArgs;
369
370         /* call the function with a singleton tuple containing the
371          * container tuple.
372          */
373
374         if ((pArgs = PyTuple_New(1)) == NULL) {
375             radlog(L_ERR, "%s: could not create tuple", function_name);
376             return -1;
377         }
378         if ((PyTuple_SetItem(pArgs, 0, pValuePairContainer)) != 0) {
379             radlog(L_ERR, "%s: could not set tuple item", function_name);
380             return -1;
381         }
382
383         if ((pValue = PyObject_CallObject(pFunc, pArgs)) == NULL) {
384             radlog(L_ERR, "%s: function call failed", function_name);
385             python_error();
386             return -1;
387         }
388
389         /* The function returns either:
390          *  1. tuple containing the integer return value,
391          *  then the integer reply code (or None to not set),
392          *  then the string tuples to build the reply with.
393          *     (returnvalue, (p1, s1), (p2, s2))
394          *
395          *  2. the function return value alone
396          *
397          *  3. None - default return value is set
398          *
399          * xxx This code is messy!
400          */
401
402         if (PyTuple_Check(pValue)) {
403             PyObject *pTupleInt;
404
405             if (PyTuple_Size(pValue) != 3) {
406                 radlog(L_ERR, "%s: tuple must be " \
407                        "(return, replyTuple, configTuple)",
408                        function_name);
409
410             }
411             else {
412                 pTupleInt = PyTuple_GetItem(pValue, 0);
413
414                 if ((pTupleInt == NULL) || !PyInt_Check(pTupleInt)) {
415                     radlog(L_ERR, "%s: first tuple element not an integer",
416                            function_name);
417                 }
418                 else {
419                     /* Now have the return value */
420                     return_value = PyInt_AsLong(pTupleInt);
421
422                     /* Reply item tuple */
423                     add_vp_tuple(&request->reply->vps,
424                                  PyTuple_GetItem(pValue, 1), function_name);
425
426                     /* Config item tuple */
427                     add_vp_tuple(&request->config_items,
428                                  PyTuple_GetItem(pValue, 2), function_name);
429                 }
430             }
431         }
432         else if (PyInt_Check(pValue)) {
433             /* Just an integer */
434             return_value = PyInt_AsLong(pValue);
435         }
436         else if (pValue == Py_None) {
437             /* returned 'None', return value defaults to "OK, continue." */
438             return_value = RLM_MODULE_OK;
439         }
440         else {
441             /* Not tuple or None */
442             radlog(L_ERR, "%s function did not return a tuple or None\n",
443                    function_name);
444         }
445
446
447         /* Decrease reference counts for the argument and return tuple */
448         Py_DECREF(pArgs);
449         Py_DECREF(pValue);
450     }
451
452     /* Decrease reference count for the tuples passed, the
453      * container tuple, and the return value.
454      */
455
456     pValueHolderPtr = pValueHolder;
457     i = n_tuple;
458     while (i--) {
459         /* Can't write as pValueHolderPtr since Py_DECREF is a macro */
460         Py_DECREF(*pValueHolderPtr);
461         pValueHolderPtr++;
462     }
463     free(pValueHolder);
464     Py_DECREF(pValuePairContainer);
465
466     /* pDict and pFunc are borrowed and must not be Py_DECREF-ed */
467
468     /* Free pairs if we are rejecting.
469      * xxx Shouldn't the core do that?
470      */
471
472     if ((return_value == RLM_MODULE_REJECT) && (request != NULL)) {
473         pairfree(&(request->reply->vps));
474     }
475
476     /* Return the specified by the Python module */
477     return return_value;
478 }
479
480
481 static struct varlookup {
482         const char*     name;
483         int             value;
484 } constants[] = {
485         { "L_DBG",              L_DBG                   },
486         { "L_AUTH",             L_AUTH                  },
487         { "L_INFO",             L_INFO                  },
488         { "L_ERR",              L_ERR                   },
489         { "L_PROXY",            L_PROXY                 },
490         { "L_CONS",             L_CONS                  },
491         { "RLM_MODULE_REJECT",  RLM_MODULE_REJECT       },
492         { "RLM_MODULE_FAIL",    RLM_MODULE_FAIL         },
493         { "RLM_MODULE_OK",      RLM_MODULE_OK           },
494         { "RLM_MODULE_HANDLED", RLM_MODULE_HANDLED      },
495         { "RLM_MODULE_INVALID", RLM_MODULE_INVALID      },
496         { "RLM_MODULE_USERLOCK",RLM_MODULE_USERLOCK     },
497         { "RLM_MODULE_NOTFOUND",RLM_MODULE_NOTFOUND     },
498         { "RLM_MODULE_NOOP",    RLM_MODULE_NOOP         },
499         { "RLM_MODULE_UPDATED", RLM_MODULE_UPDATED      },
500         { "RLM_MODULE_NUMCODES",RLM_MODULE_NUMCODES     },
501         { NULL, 0 },
502 };
503
504 /*
505  * Import a user module and load a function from it
506  */
507 static int load_python_function(const char* module, const char* func,
508                                 PyObject** pyModule, PyObject** pyFunc) {
509
510     if ((module==NULL) || (func==NULL)) {
511         *pyFunc=NULL;
512         *pyModule=NULL;
513     } else {
514         PyObject *pName;
515
516         pName = PyString_FromString(module);
517         Py_INCREF(pName);
518         *pyModule = PyImport_Import(pName);
519         Py_DECREF(pName);
520         if (*pyModule != NULL) {
521             PyObject *pDict;
522
523             pDict = PyModule_GetDict(*pyModule);
524             /* pDict: borrowed reference */
525
526             *pyFunc = PyDict_GetItemString(pDict, func);
527             /* pFunc: Borrowed reference */
528         } else {
529             python_error();
530
531             radlog(L_ERR, "Failed to import python module \"%s\"\n", module);
532             return -1;
533         }
534     }
535
536     return 0;
537 }
538
539
540 /*
541  *      Do any per-module initialization that is separate to each
542  *      configured instance of the module.  e.g. set up connections
543  *      to external databases, read configuration files, set up
544  *      dictionary entries, etc.
545  *
546  *      If configuration information is given in the config section
547  *      that must be referenced in later calls, store a handle to it
548  *      in *instance otherwise put a null pointer there.
549  *
550  */
551 static int python_instantiate(CONF_SECTION *conf, void **instance)
552 {
553     rlm_python_t *data;
554     PyObject *module;
555     int idx;
556
557     /*
558      * Initialize Python interpreter. Fatal error if this fails.
559      */
560     Py_Initialize();
561
562     /*
563      *  Set up a storage area for instance data
564      */
565     data = rad_malloc(sizeof(*data));
566     if (!data) {
567       return -1;
568     }
569     memset(data, 0, sizeof(*data));
570
571     /*
572          *      If the configuration parameters can't be parsed, then
573          *      fail.
574          */
575     if (cf_section_parse(conf, data, module_config) < 0) {
576         free(data);
577         return -1;
578     }
579
580
581     /*
582      * Setup our 'radiusd' module.
583      */
584
585     /* Code */
586     if ((module = data->pModule_builtin =
587          Py_InitModule3("radiusd", radiusd_methods,
588                         "FreeRADIUS Module.")) == NULL) {
589
590         radlog(L_ERR, "Python Py_InitModule3 failed");
591         free(data);
592         return -1;
593     }
594
595     /*
596      * Load constants into module
597      */
598     for (idx=0; constants[idx].name; idx++)
599         if ((PyModule_AddIntConstant(module, constants[idx].name, constants[idx].value)) == -1) {
600
601             radlog(L_ERR, "Python AddIntConstant failed");
602         }
603
604
605     /*
606      * Import user modules.
607      */
608
609     if (load_python_function(data->mod_instantiate, data->func_instantiate,
610                 &data->pModule_instantiate, &data->pFunc_instantiate)==-1) {
611         /* TODO: check if we need to cleanup data */
612         return -1;
613     }
614
615     if (load_python_function(data->mod_authenticate, data->func_authenticate,
616                 &data->pModule_authenticate, &data->pFunc_authenticate)==-1) {
617         /* TODO: check if we need to cleanup data */
618         return -1;
619     }
620
621     if (load_python_function(data->mod_authorize, data->func_authorize,
622                 &data->pModule_authorize, &data->pFunc_authorize)==-1) {
623         /* TODO: check if we need to cleanup data */
624         return -1;
625     }
626
627     if (load_python_function(data->mod_preacct, data->func_preacct,
628                 &data->pModule_preacct, &data->pFunc_preacct)==-1) {
629         /* TODO: check if we need to cleanup data */
630         return -1;
631     }
632
633     if (load_python_function(data->mod_accounting, data->func_accounting,
634                 &data->pModule_accounting, &data->pFunc_accounting)==-1) {
635         /* TODO: check if we need to cleanup data */
636         return -1;
637     }
638
639     if (load_python_function(data->mod_checksimul, data->func_checksimul,
640                 &data->pModule_checksimul, &data->pFunc_checksimul)==-1) {
641         /* TODO: check if we need to cleanup data */
642         return -1;
643     }
644
645     if (load_python_function(data->mod_detach, data->func_detach,
646                 &data->pModule_detach, &data->pFunc_detach)==-1) {
647         /* TODO: check if we need to cleanup data */
648         return -1;
649     }
650
651     *instance=data;
652
653     /* Call the instantiate function.  No request.  Use the return value. */
654     return python_function(NULL, data->pFunc_instantiate, "instantiate");
655 }
656
657 /* Wrapper functions */
658 static int python_authorize(void *instance, REQUEST *request)
659 {
660     return python_function(request,
661                            ((struct rlm_python_t *)instance)->pFunc_authorize,
662                            "authorize");
663 }
664
665 static int python_authenticate(void *instance, REQUEST *request)
666 {
667     return python_function(
668         request,
669         ((struct rlm_python_t *)instance)->pFunc_authenticate,
670         "authenticate");
671 }
672
673 static int python_preacct(void *instance, REQUEST *request)
674 {
675     return python_function(
676         request,
677         ((struct rlm_python_t *)instance)->pFunc_preacct,
678         "preacct");
679 }
680
681 static int python_accounting(void *instance, REQUEST *request)
682 {
683     return python_function(
684         request,
685         ((struct rlm_python_t *)instance)->pFunc_accounting,
686         "accounting");
687 }
688
689 static int python_checksimul(void *instance, REQUEST *request)
690 {
691     return python_function(
692         request,
693         ((struct rlm_python_t *)instance)->pFunc_checksimul,
694         "checksimul");
695 }
696
697
698 static int python_detach(void *instance)
699 {
700     int return_value;
701
702     /* Default return value is failure */
703     return_value = -1;
704
705     if (((rlm_python_t *)instance)->pFunc_detach &&
706         PyCallable_Check(((rlm_python_t *)instance)->pFunc_detach)) {
707
708         PyObject *pArgs, *pValue;
709
710         /* call the function with an empty tuple */
711
712         pArgs = PyTuple_New(0);
713         pValue = PyObject_CallObject(((rlm_python_t *)instance)->pFunc_detach,
714                                      pArgs);
715
716         if (pValue == NULL) {
717             python_error();
718             return -1;
719         }
720         else {
721             if (!PyInt_Check(pValue)) {
722                 radlog(L_ERR, "detach: return value not an integer");
723             }
724             else {
725                 return_value = PyInt_AsLong(pValue);
726             }
727         }
728
729         /* Decrease reference counts for the argument and return tuple */
730         Py_DECREF(pArgs);
731         Py_DECREF(pValue);
732     }
733
734     free(instance);
735
736 #if 0
737     /* xxx test delete module object so it will be reloaded later.
738      * xxx useless since we can't SIGHUP reliably, anyway.
739      */
740     PyObject_Del(((struct rlm_python_t *)instance)->pModule_accounting);
741 #endif
742
743     radlog(L_DBG, "python_detach done");
744
745     /* Return the specified by the Python module */
746     return return_value;
747 }
748
749 /*
750  *      The module name should be the only globally exported symbol.
751  *      That is, everything else should be 'static'.
752  *
753  *      If the module needs to temporarily modify it's instantiation
754  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
755  *      The server will then take care of ensuring that the module
756  *      is single-threaded.
757  */
758 module_t rlm_python = {
759         RLM_MODULE_INIT,
760         "python",
761         RLM_TYPE_THREAD_SAFE,           /* type */
762         python_instantiate,             /* instantiation */
763         python_detach,                  /* detach */
764         {
765                 python_authenticate,    /* authentication */
766                 python_authorize,       /* authorization */
767                 python_preacct,         /* preaccounting */
768                 python_accounting,      /* accounting */
769                 python_checksimul,      /* checksimul */
770                 NULL,                   /* pre-proxy */
771                 NULL,                   /* post-proxy */
772                 NULL                    /* post-auth */
773         },
774 };