2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief Translates requests between the server an a python interpreter.
22 * @note Rewritten by Paul P. Komkoff Jr <i@stingr.net>.
24 * @copyright 2000,2006 The FreeRADIUS server project
25 * @copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com>
26 * @copyright 2002 Imperium Technology, Inc.
30 #include <freeradius-devel/radiusd.h>
31 #include <freeradius-devel/modules.h>
37 #define Pyx_BLOCK_THREADS {PyGILState_STATE __gstate = PyGILState_Ensure();
38 #define Pyx_UNBLOCK_THREADS PyGILState_Release(__gstate);}
40 #define Pyx_BLOCK_THREADS
41 #define Pyx_UNBLOCK_THREADS
45 * TODO: The only needed thing here is function. Anything else is
46 * required for initialization only. I will remove it, putting a
47 * symbolic constant here instead.
49 struct py_function_def {
53 char const *module_name;
54 char const *function_name;
57 typedef struct rlm_python_t {
59 PyThreadState *main_thread_state;
60 char const *python_path;
62 struct py_function_def
80 * A mapping of configuration file names to internal variables.
82 static CONF_PARSER module_config[] = {
84 #define A(x) { "mod_" #x, FR_CONF_OFFSET(PW_TYPE_STRING, rlm_python_t, x.module_name), NULL }, \
85 { "func_" #x, FR_CONF_OFFSET(PW_TYPE_STRING, rlm_python_t, x.function_name), NULL },
104 { "python_path", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_python_t, python_path), NULL },
105 CONF_PARSER_TERMINATOR
111 } radiusd_constants[] = {
113 #define A(x) { #x, x },
129 A(RLM_MODULE_HANDLED)
130 A(RLM_MODULE_INVALID)
131 A(RLM_MODULE_USERLOCK)
132 A(RLM_MODULE_NOTFOUND)
134 A(RLM_MODULE_UPDATED)
135 A(RLM_MODULE_NUMCODES)
143 * This allows us to initialise PyThreadState on a per thread basis
145 fr_thread_local_setup(PyThreadState *, local_thread_state) /* macro */
149 * Let assume that radiusd module is only one since we have only
153 static PyObject *radiusd_module = NULL;
156 * radiusd Python functions
160 static PyObject *mod_radlog(UNUSED PyObject *module, PyObject *args)
165 if (!PyArg_ParseTuple(args, "is", &status, &msg)) {
169 radlog(status, "%s", msg);
175 static PyMethodDef radiusd_methods[] = {
176 { "radlog", &mod_radlog, METH_VARARGS,
177 "radiusd.radlog(level, msg)\n\n" \
178 "Print a message using radiusd logging system. level should be one of the\n" \
179 "constants L_DBG, L_AUTH, L_INFO, L_ERR, L_PROXY\n"
181 { NULL, NULL, 0, NULL },
185 static void mod_error(void)
187 PyObject *pType = NULL, *pValue = NULL, *pTraceback = NULL, *pStr1 = NULL, *pStr2 = NULL;
189 /* This will be called with the GIL lock held */
191 PyErr_Fetch(&pType, &pValue, &pTraceback);
192 if (!pType || !pValue)
194 if (((pStr1 = PyObject_Str(pType)) == NULL) ||
195 ((pStr2 = PyObject_Str(pValue)) == NULL))
198 ERROR("rlm_python:EXCEPT:%s: %s", PyString_AsString(pStr1), PyString_AsString(pStr2));
205 Py_XDECREF(pTraceback);
208 static int mod_init(rlm_python_t *inst)
211 static char name[] = "radiusd";
213 if (radiusd_module) return 0;
216 * Explicitly load libpython, so symbols will be available to lib-dynload modules
218 inst->libpython = dlopen("libpython" STRINGIFY(PY_MAJOR_VERSION) "." STRINGIFY(PY_MINOR_VERSION) ".so",
219 RTLD_NOW | RTLD_GLOBAL);
220 if (!inst->libpython) {
221 WARN("Failed loading libpython symbols into global symbol table: %s", dlerror());
224 Py_SetProgramName(name);
225 #ifdef HAVE_PTHREAD_H
226 Py_InitializeEx(0); /* Don't override signal handlers */
227 PyEval_InitThreads(); /* This also grabs a lock */
228 inst->main_thread_state = PyThreadState_Get(); /* We need this for setting up thread local stuff */
230 if (inst->python_path) {
233 memcpy(&path, &inst->python_path, sizeof(path));
237 if ((radiusd_module = Py_InitModule3("radiusd", radiusd_methods,
238 "FreeRADIUS Module")) == NULL)
241 for (i = 0; radiusd_constants[i].name; i++) {
242 if ((PyModule_AddIntConstant(radiusd_module, radiusd_constants[i].name,
243 radiusd_constants[i].value)) < 0) {
248 #ifdef HAVE_PTHREAD_H
249 PyThreadState_Swap(NULL); /* We have to swap out the current thread else we get deadlocks */
250 PyEval_ReleaseLock(); /* Drop lock grabbed by InitThreads */
252 DEBUG("mod_init done");
256 Py_XDECREF(radiusd_module);
258 #ifdef HAVE_PTHREAD_H
259 PyEval_ReleaseLock();
266 radiusd_module = NULL;
274 static int mod_destroy(void)
277 Py_XDECREF(radiusd_module);
285 * This will need reconsidering in a future. Maybe we'll need to
286 * have our own reference counting for radiusd_module
290 /* TODO: Convert this function to accept any iterable objects? */
292 static void mod_vptuple(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, PyObject *pValue,
293 char const *funcname, char const *list_name)
299 REQUEST *current = request;
301 memset(&dst, 0, sizeof(dst));
304 * If the Python function gave us None for the tuple,
307 if (pValue == Py_None)
310 if (!PyTuple_CheckExact(pValue)) {
311 ERROR("rlm_python:%s: non-tuple passed to %s", funcname, list_name);
314 /* Get the tuple tuplesize. */
315 tuplesize = PyTuple_GET_SIZE(pValue);
316 for (i = 0; i < tuplesize; i++) {
317 PyObject *pTupleElement = PyTuple_GET_ITEM(pValue, i);
324 FR_TOKEN op = T_OP_EQ;
326 if (!PyTuple_CheckExact(pTupleElement)) {
327 ERROR("rlm_python:%s: tuple element %d of %s is not a tuple", funcname, i, list_name);
330 /* Check if it's a pair */
332 pairsize = PyTuple_GET_SIZE(pTupleElement);
333 if ((pairsize < 2) || (pairsize > 3)) {
334 ERROR("rlm_python:%s: tuple element %d of %s is a tuple of size %d. Must be 2 or 3.", funcname, i, list_name, pairsize);
339 pStr1 = PyTuple_GET_ITEM(pTupleElement, 0);
340 pStr2 = PyTuple_GET_ITEM(pTupleElement, 1);
342 pStr1 = PyTuple_GET_ITEM(pTupleElement, 0);
343 pStr2 = PyTuple_GET_ITEM(pTupleElement, 2);
344 pOp = PyTuple_GET_ITEM(pTupleElement, 1);
345 if (PyInt_Check(pOp)) {
346 op = PyInt_AsLong(pOp);
347 if (!fr_int2str(fr_tokens, op, NULL)) {
348 ERROR("rlm_python:%s: Invalid operator '%i', falling back to '='", funcname, op);
351 } else if (PyString_CheckExact(pOp)) {
352 if (!(op = fr_str2int(fr_tokens, PyString_AsString(pOp), 0))) {
353 ERROR("rlm_python:%s: Invalid operator '%s', falling back to '='", funcname, PyString_AsString(pOp));
357 ERROR("rlm_python:%s: Invalid operator type, using default '='", funcname);
361 if ((!PyString_CheckExact(pStr1)) || (!PyString_CheckExact(pStr2))) {
362 ERROR("rlm_python:%s: tuple element %d of %s must be as (str, str)", funcname, i, list_name);
365 s1 = PyString_AsString(pStr1);
366 s2 = PyString_AsString(pStr2);
368 if (tmpl_from_attr_str(&dst, s1, REQUEST_CURRENT, PAIR_LIST_REPLY, false, false) <= 0) {
369 DEBUG("rlm_python:%s: Failed to find attribute %s:%s", funcname, list_name, s1);
373 if (radius_request(¤t, dst.tmpl_request) < 0) {
374 DEBUG("rlm_python:%s: Attribute name %s:%s refers to outer request but not in a tunnel, skipping...", funcname, list_name, s1);
377 if (!(vp = fr_pair_afrom_da(ctx, dst.tmpl_da))) {
378 DEBUG("rlm_python:%s: Failed to create attribute %s:%s", funcname, list_name, s1);
383 if (fr_pair_value_from_str(vp, s2, -1) < 0) {
384 DEBUG("rlm_python:%s: Failed: '%s:%s' %s '%s'", funcname, list_name, s1, fr_int2str(fr_tokens, op, "="), s2);
386 DEBUG("rlm_python:%s: '%s:%s' %s '%s'", funcname, list_name, s1, fr_int2str(fr_tokens, op, "="), s2);
389 radius_pairmove(current, vps, vp, false);
395 * This is the core Python function that the others wrap around.
396 * Pass the value-pair print strings in a tuple.
398 * FIXME: We're not checking the errors. If we have errors, what
401 static int mod_populate_vptuple(PyObject *pPair, VALUE_PAIR *vp)
403 PyObject *pStr = NULL;
406 /* Look at the vp_print_name? */
408 if (vp->da->flags.has_tag)
409 pStr = PyString_FromFormat("%s:%d", vp->da->name, vp->tag);
411 pStr = PyString_FromString(vp->da->name);
416 PyTuple_SET_ITEM(pPair, 0, pStr);
418 vp_prints_value(buf, sizeof(buf), vp, '"');
420 if ((pStr = PyString_FromString(buf)) == NULL)
422 PyTuple_SET_ITEM(pPair, 1, pStr);
430 #ifdef HAVE_PTHREAD_H
431 /** Cleanup any thread local storage on pthread_exit()
433 static void do_python_cleanup(void *arg)
435 PyThreadState *my_thread_state = arg;
437 PyEval_AcquireLock();
438 PyThreadState_Swap(NULL); /* Not entirely sure this is needed */
439 PyThreadState_Clear(my_thread_state);
440 PyThreadState_Delete(my_thread_state);
441 PyEval_ReleaseLock();
445 static rlm_rcode_t do_python(rlm_python_t *inst, REQUEST *request, PyObject *pFunc, char const *funcname, bool worker)
449 PyObject *pRet = NULL;
450 PyObject *pArgs = NULL;
454 PyGILState_STATE gstate;
455 PyThreadState *prev_thread_state = NULL; /* -Wuninitialized */
456 memset(&gstate, 0, sizeof(gstate)); /* -Wuninitialized */
458 /* Return with "OK, continue" if the function is not defined. */
460 return RLM_MODULE_NOOP;
462 #ifdef HAVE_PTHREAD_H
463 gstate = PyGILState_Ensure();
465 PyThreadState *my_thread_state;
466 my_thread_state = fr_thread_local_init(local_thread_state, do_python_cleanup);
467 if (!my_thread_state) {
468 my_thread_state = PyThreadState_New(inst->main_thread_state->interp);
469 RDEBUG3("Initialised new thread state %p", my_thread_state);
470 if (!my_thread_state) {
471 REDEBUG("Failed initialising local PyThreadState on first run");
472 PyGILState_Release(gstate);
473 return RLM_MODULE_FAIL;
476 ret = fr_thread_local_set(local_thread_state, my_thread_state);
478 REDEBUG("Failed storing PyThreadState in TLS: %s", fr_syserror(ret));
479 PyThreadState_Clear(my_thread_state);
480 PyThreadState_Delete(my_thread_state);
481 PyGILState_Release(gstate);
482 return RLM_MODULE_FAIL;
485 RDEBUG3("Using thread state %p", my_thread_state);
486 prev_thread_state = PyThreadState_Swap(my_thread_state); /* Swap in our local thread state */
490 /* Default return value is "OK, continue" */
494 * We will pass a tuple containing (name, value) tuples
495 * We can safely use the Python function to build up a
496 * tuple, since the tuple is not used elsewhere.
498 * Determine the size of our tuple by walking through the packet.
499 * If request is NULL, pass None.
502 if (request != NULL) {
503 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
505 vp = fr_cursor_next(&cursor)) {
515 if ((pArgs = PyTuple_New(tuplelen)) == NULL) {
516 ret = RLM_MODULE_FAIL;
520 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
522 vp = fr_cursor_next(&cursor), i++) {
525 /* The inside tuple has two only: */
526 if ((pPair = PyTuple_New(2)) == NULL) {
527 ret = RLM_MODULE_FAIL;
531 if (mod_populate_vptuple(pPair, vp) == 0) {
532 /* Put the tuple inside the container */
533 PyTuple_SET_ITEM(pArgs, i, pPair);
536 PyTuple_SET_ITEM(pArgs, i, Py_None);
542 /* Call Python function. */
543 pRet = PyObject_CallFunctionObjArgs(pFunc, pArgs, NULL);
546 ret = RLM_MODULE_FAIL;
554 * The function returns either:
555 * 1. (returnvalue, replyTuple, configTuple), where
556 * - returnvalue is one of the constants RLM_*
557 * - replyTuple and configTuple are tuples of string
560 * 2. the function return value alone
562 * 3. None - default return value is set
564 * xxx This code is messy!
566 if (PyTuple_CheckExact(pRet)) {
569 if (PyTuple_GET_SIZE(pRet) != 3) {
570 ERROR("rlm_python:%s: tuple must be (return, replyTuple, configTuple)", funcname);
571 ret = RLM_MODULE_FAIL;
575 pTupleInt = PyTuple_GET_ITEM(pRet, 0);
576 if (!PyInt_CheckExact(pTupleInt)) {
577 ERROR("rlm_python:%s: first tuple element not an integer", funcname);
578 ret = RLM_MODULE_FAIL;
581 /* Now have the return value */
582 ret = PyInt_AsLong(pTupleInt);
583 /* Reply item tuple */
584 mod_vptuple(request->reply, request, &request->reply->vps,
585 PyTuple_GET_ITEM(pRet, 1), funcname, "reply");
586 /* Config item tuple */
587 mod_vptuple(request, request, &request->config,
588 PyTuple_GET_ITEM(pRet, 2), funcname, "config");
590 } else if (PyInt_CheckExact(pRet)) {
591 /* Just an integer */
592 ret = PyInt_AsLong(pRet);
594 } else if (pRet == Py_None) {
595 /* returned 'None', return value defaults to "OK, continue." */
598 /* Not tuple or None */
599 ERROR("rlm_python:%s: function did not return a tuple or None", funcname);
600 ret = RLM_MODULE_FAIL;
608 #ifdef HAVE_PTHREAD_H
610 PyThreadState_Swap(prev_thread_state);
612 PyGILState_Release(gstate);
619 * Import a user module and load a function from it
622 static int mod_load_function(struct py_function_def *def)
624 char const *funcname = "mod_load_function";
625 PyGILState_STATE gstate;
627 gstate = PyGILState_Ensure();
629 if (def->module_name != NULL && def->function_name != NULL) {
630 if ((def->module = PyImport_ImportModule(def->module_name)) == NULL) {
631 ERROR("rlm_python:%s: module '%s' is not found", funcname, def->module_name);
635 if ((def->function = PyObject_GetAttrString(def->module, def->function_name)) == NULL) {
636 ERROR("rlm_python:%s: function '%s.%s' is not found", funcname, def->module_name, def->function_name);
640 if (!PyCallable_Check(def->function)) {
641 ERROR("rlm_python:%s: function '%s.%s' is not callable", funcname, def->module_name, def->function_name);
645 PyGILState_Release(gstate);
650 ERROR("rlm_python:%s: failed to import python function '%s.%s'", funcname, def->module_name, def->function_name);
651 Py_XDECREF(def->function);
652 def->function = NULL;
653 Py_XDECREF(def->module);
655 PyGILState_Release(gstate);
660 static void mod_objclear(PyObject **ob)
670 static void mod_funcdef_clear(struct py_function_def *def)
672 mod_objclear(&def->function);
673 mod_objclear(&def->module);
676 static void mod_instance_clear(rlm_python_t *inst)
678 #define A(x) mod_funcdef_clear(&inst->x)
692 * Do any per-module initialization that is separate to each
693 * configured instance of the module. e.g. set up connections
694 * to external databases, read configuration files, set up
695 * dictionary entries, etc.
697 * If configuration information is given in the config section
698 * that must be referenced in later calls, store a handle to it
699 * in *instance otherwise put a null pointer there.
702 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
704 rlm_python_t *inst = instance;
706 if (mod_init(inst) != 0) {
710 #define A(x) if (mod_load_function(&inst->x) < 0) goto failed
730 * Call the instantiate function. No request. Use the
733 return do_python(inst, NULL, inst->instantiate.function, "instantiate", false);
738 mod_instance_clear(inst);
742 static int mod_detach(void *instance)
744 rlm_python_t *inst = instance;
748 * Master should still have no thread state
750 ret = do_python(inst, NULL, inst->detach.function, "detach", false);
752 mod_instance_clear(inst);
753 dlclose(inst->libpython);
758 #define A(x) static rlm_rcode_t CC_HINT(nonnull) mod_##x(void *instance, REQUEST *request) { \
759 return do_python((rlm_python_t *) instance, request, ((rlm_python_t *)instance)->x.function, #x, true);\
778 * The module name should be the only globally exported symbol.
779 * That is, everything else should be 'static'.
781 * If the module needs to temporarily modify it's instantiation
782 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
783 * The server will then take care of ensuring that the module
784 * is single-threaded.
786 extern module_t rlm_python;
787 module_t rlm_python = {
788 .magic = RLM_MODULE_INIT,
790 .type = RLM_TYPE_THREAD_SAFE,
791 .inst_size = sizeof(rlm_python_t),
792 .config = module_config,
793 .instantiate = mod_instantiate,
794 .detach = mod_detach,
796 [MOD_AUTHENTICATE] = mod_authenticate,
797 [MOD_AUTHORIZE] = mod_authorize,
798 [MOD_PREACCT] = mod_preacct,
799 [MOD_ACCOUNTING] = mod_accounting,
800 [MOD_SESSION] = mod_checksimul,
801 [MOD_PRE_PROXY] = mod_pre_proxy,
802 [MOD_POST_PROXY] = mod_post_proxy,
803 [MOD_POST_AUTH] = mod_post_auth,
805 [MOD_RECV_COA] = mod_recv_coa,
806 [MOD_SEND_COA] = mod_send_coa