From: Jorge Pereira Date: Mon, 2 Jan 2023 22:28:09 +0000 (-0300) Subject: pyfr: Add Python binding for libfreeradius API. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6522cff2eb2cb84df48a0c389d5d3723dd53a813;p=thirdparty%2Ffreeradius-server.git pyfr: Add Python binding for libfreeradius API. The first version of our new Python "pyfr" module exporting some API behaviors. --- diff --git a/Make.inc.in b/Make.inc.in index 2f58eb7165..94ca638c8d 100644 --- a/Make.inc.in +++ b/Make.inc.in @@ -222,3 +222,10 @@ PANDOC_ENGINE := @PANDOC_ENGINE@ DOXYGEN := @DOXYGEN@ GRAPHVIZ_DOT := @GRAPHVIZ_DOT@ ANTORA := @ANTORA@ + +# +# All supported binding languages. +# +comma := , +ENABLED_LANGUAGES = @ENABLED_LANGUAGES@ +ENABLED_LANGUAGES_LIST = $(subst $(comma), ,$(ENABLED_LANGUAGES)) diff --git a/configure b/configure index ee52c292c6..3944efd5cf 100755 --- a/configure +++ b/configure @@ -726,6 +726,7 @@ build_vendor build_cpu build RADIUSD_VERSION_COMMIT +ENABLED_LANGUAGES ANTORA GRAPHVIZ_DOT DOXYGEN @@ -781,6 +782,7 @@ ac_subst_files='' ac_user_opts=' enable_option_checking enable_developer +enable_language enable_verify_ptr enable_largefile enable_strict_dependencies @@ -1470,6 +1472,7 @@ Optional Features: --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-developer enables features of interest to developers. + --enable-language enables languages binding availables in src/language/. e.g: ${available_languages} --disable-verify-ptr disables WITH_VERIFY_PTR developer build option. --disable-largefile omit support for large files --enable-strict-dependencies fail configure on lack of module dependancy. @@ -3275,6 +3278,23 @@ if test "x$developer" = "xyes"; then printf "%s\n" "$as_me: Enabling developer build implicitly, disable with --disable-developer" >&6;} fi +available_languages=`for _i in src/language/*; do test -d $_i && echo "${_i/*\//},"; done | tr -d '\n' | sed 's@,$@@'` +# Check whether --enable-language was given. +if test ${enable_language+y} +then : + enableval=$enable_language; case "$enableval" in + no) + enabled_languages= + ;; + *) + enabled_languages=${enableval} + esac + +fi + +ENABLED_LANGUAGES="${enabled_languages}" + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build commit" >&5 printf %s "checking build commit... " >&6; } RADIUSD_VERSION_COMMIT=`./version.sh commit` diff --git a/configure.ac b/configure.ac index 7cdf672afc..c8adc2c707 100644 --- a/configure.ac +++ b/configure.ac @@ -159,6 +159,22 @@ if test "x$developer" = "xyes"; then AC_MSG_NOTICE([Enabling developer build implicitly, disable with --disable-developer]) fi +dnl # +dnl # Enable languages binding availables in src/language/ +dnl # +available_languages=`for _i in src/language/*; do test -d $_i && echo "${_i/*\//},"; done | tr -d '\n' | sed 's@,$@@'` +AC_ARG_ENABLE(language, +[ --enable-language enables languages binding availables in src/language/. e.g: ${available_languages}], +[ case "$enableval" in + no) + enabled_languages= + ;; + *) + enabled_languages=${enableval} + esac ] +) +AC_SUBST(ENABLED_LANGUAGES, "${enabled_languages}") + dnl # dnl # Write the current commit into Make.inc dnl # diff --git a/src/all.mk b/src/all.mk index 716cd45927..5e7d23cc92 100644 --- a/src/all.mk +++ b/src/all.mk @@ -16,3 +16,17 @@ SUBMAKEFILES := include/all.mk \ ifneq "$(findstring test,$(MAKECMDGOALS))$(findstring clean,$(MAKECMDGOALS))" "" SUBMAKEFILES += tests/all.mk endif + +# +# Define a function to do all of the same thing. +# +ifneq "$(ENABLED_LANGUAGES)" "" +define ENABLE_LANGUAGE +language/${1}/all.mk: + $${Q}echo "ENABLE LANGUAGE ${1}" + +SUBMAKEFILES += language/${1}/all.mk +endef + +$(foreach L,${ENABLED_LANGUAGES_LIST},$(eval $(call ENABLE_LANGUAGE,${L}))) +endif diff --git a/src/language/python/.gitignore b/src/language/python/.gitignore new file mode 100644 index 0000000000..acb3035335 --- /dev/null +++ b/src/language/python/.gitignore @@ -0,0 +1,3 @@ +pyfr.egg-info/ +dist/ +build/ diff --git a/src/language/python/PKG-INFO b/src/language/python/PKG-INFO new file mode 100644 index 0000000000..3ff7d45202 --- /dev/null +++ b/src/language/python/PKG-INFO @@ -0,0 +1,60 @@ +Metadata-Version: 1.0 +Name: pyfr +Version: 0.0.1 +Summary: PyFR -- A Python Interface To The libfreeradius-* libraries +Home-page: https://github.com/FreeRADIUS/freeradius-server/tree/master/src/languages/python/ +Author: Jorge Pereira +Author-email: freeradius-devel@lists.freeradius.org +Maintainer: FreeRADIUS Project +Maintainer-email: freeradius-devel@lists.freeradius.org +License: GPL +Description: PyFR -- A Python Interface To The libfreeradius-* libraries + ================================================ + + PyFR is a Python interface to `libfreeradius`_, the multiprotocol base + library. + PyFR can be used to RADIUS, TACACS, TFTP and DNS protocols. + + Requirements + ------------ + + - Python 3.10. + - FreeRADIUS 4.0.0 or better. + + Installation + ------------ + + Download the source distribution from `PyPI`_. + + Please see `the installation documentation`_ for installation instructions. + + Support + ------- + + For support questions please use `freeradius-devel mailing list`_. + `Mailing list archives`_ are available for your perusal as well. + + Bugs can be reported `via GitHub`_. Please use GitHub only for bug + reports and direct questions to our mailing list instead. + + .. _freeradius-devel mailing list: https://lists.freeradius.org/mailman/listinfo/freeradius-devel + .. _Mailing list archives: https://lists.freeradius.org/pipermail/freeradius-devel/ + .. _via GitHub: https://github.com/FreeRADIUS/freeradius-server/issues + + + License + ------- + + PyFR is licensed under the GPL license. The complete text of the licenses is available + in COPYING-GPL_ files in the source distribution. + + .. _COPYING-LGPL: https://github.com/FreeRADIUS/freeradius-server/blob/master/LICENSE + +Keywords: freeradius,libfreeradius,pyfr,radius,aaa +Platform: All +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or General Public License (GPL) +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3.10 +Classifier: Topic :: Internet :: RADIUS +Requires-Python: >=3.10 diff --git a/src/language/python/README.md b/src/language/python/README.md new file mode 100644 index 0000000000..ce45bd4706 --- /dev/null +++ b/src/language/python/README.md @@ -0,0 +1,12 @@ +PyFR -- A Python Interface To The FreeRADIUS libraries +================================================ + + +PyFR is a Python interface to `libfreeradius-*`_. + + +Requirements +------------ + +- Python 3.10. +- libfreeradius 4.0.0 or better. diff --git a/src/language/python/TESTING.txt b/src/language/python/TESTING.txt new file mode 100644 index 0000000000..4c517dde43 --- /dev/null +++ b/src/language/python/TESTING.txt @@ -0,0 +1,26 @@ +Brief about how to build and test it! + +1. The "eapol_test" should support the _Ctrl_ command 'GET_RADIUS_REPLY'. In this case, we must use the below repo/tag. + +e.g: + +``` +$ export HOSTAPD_REPO="https://github.com/NetworkRADIUS/hostap" +$ export HOSTAPD_GIT_TAG="feature/get_radius_reply" +$ ./scripts/ci/eapol_test-build.sh +``` + +2. Assuming the branch 'v4/pyfr' is properly merged in 'master' branch. + +``` +$ cd src/language/python +$ make clean install +``` + +3. The below test script should work! + +``` +$ ./tests/test.py +$ ./examples/radict.py User-Name +``` + diff --git a/src/language/python/all.mk b/src/language/python/all.mk new file mode 100644 index 0000000000..e1982389f1 --- /dev/null +++ b/src/language/python/all.mk @@ -0,0 +1,38 @@ +TARGETNAME := build.language.python +TGT_PREREQS := libfreeradius-internal$(L) libfreeradius-util$(L) libfreeradius-radius$(L) + +SOURCES = src/language/python/src/module.c \ + src/language/python/src/radius.c \ + src/language/python/src/util.c + +ifneq "${VERBOSE}" "" + export DISTUTILS_DEBUG=1 + export VERBOSE=1 +endif + +export CFLAGS CPPFLAGS LDFLAGS LIBS top_builddir + +build/language/python: + $(Q)mkdir -p $@ + +build.language.python: $(SOURCES) + @echo "BUILD LANGUAGE python (pyfr)" + $(Q)cd src/language/python/ && python3 setup.py -v build + +install.language.python: build/language/python build.language.python + @echo "INSTALL LANGUAGE python (pyfr)" + $(Q)cd src/language/python/ && python3 setup.py install --record $(top_builddir)/build/language/python/install.txt + +uninstall.language.python: + @echo "UNINSTALL LANGUAGE python (pyfr)" + $(Q)xargs rm -rfv < $(top_builddir)/build/language/python/install.txt + +clean.language.python: + @echo "CLEAN LANGUAGE python (pyfr)" + $(Q)cd src/language/python/ && python3 setup.py clean + $(Q)rm -f *~ + $(Q)rm -rf build + +test.language.python: language.python.build + @echo "TEST LANGUAGE python (pyfr)" + $(Q)cd src/language/python/ && ./tests/run.sh diff --git a/src/language/python/examples/radict.py b/src/language/python/examples/radict.py new file mode 100755 index 0000000000..6fd9e0aee9 --- /dev/null +++ b/src/language/python/examples/radict.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# +# Test script for pyfr +# Copyright 2023 The FreeRADIUS server project +# Author: Jorge Pereira (jpereira@freeradius.org) +# + +import argparse +import json +import sys +import textwrap + +try: + import pyfr +except Exception as e: + print("Please install first the 'pyfr'") + sys.exit(-1) + +raddb_dir = "../../../raddb" +dict_dir = "../../../share/dictionary" +lib_dir = "../../../build/lib/local/.libs/" + +def load_args(): + parser = argparse.ArgumentParser(formatter_class = argparse.RawDescriptionHelpFormatter, + epilog = "Very simple interface to extract attribute definitions from FreeRADIUS dictionaries") + + parser.add_argument("attribute", nargs='+', help="List of attributes.. (e.g: NAS-Port-Id ... User-Password)") + parser.add_argument("-E", + dest='export', + help = "Export dictionary definitions.", + action = "store_true", + required = False, + default = False + ) + parser.add_argument("-V", + dest = "all_attributes", + help = "Write out all attribute values.", + action = "store_true", + required = False, + default = False + ) + parser.add_argument("-D", + dest = "dict_dir", + help = "Set main dictionary directory (defaults to {})".format(pyfr.DICTDIR), + required = False, + default = pyfr.DICTDIR + ) + parser.add_argument("-d", + dest = "raddb_dir", + help = "Set configuration directory (defaults {})".format(pyfr.RADDBDIR), + required = False, + default = pyfr.RADDBDIR + ) + parser.add_argument("-p", + dest = "protocol", + help = "Set protocol by name", + required = False, + default = "radius" + ) + parser.add_argument("-x", + dest = "debug", + help = "Debugging mode.", + action = 'count', + required = False, + default = 0 + ) + parser.add_argument("-c", + dest = "all_attributes", + help = "Print out in CSV format.", + action = "store_true", + required = False, + default = False + ) + parser.add_argument("-H", + dest = "show_headers", + help = "Show the headers of each field.", + action = "store_true", + required = False, + default = False + ) + parser.add_argument("-v", + dest = "verbose", + help = "Verbose mode. (e.g: -vvv)", + action = 'count', + required = False, + default = 0 + ) + + return parser.parse_args() + +def radict_export(ret, args): + print("TODO radict_export()") + +if __name__ == "__main__": + try: + args = load_args() + + fr = pyfr.PyFR() + fr.set_debug_level(args.verbose) + fr.set_raddb_dir(args.raddb_dir) + fr.set_dict_dir(args.dict_dir) + # fr.set_lib_dir(args.lib_dir) + + util = fr.Util() + + if args.show_headers: + print("Dictionary\tOID\tAttribute\tID\tType\tFlags") + + ret = {} + i = 0 + for attr in args.attribute: + if args.debug: + print("Looking for {}".format(attr)) + + ret[i] = util.dict_attr_by_oid(attr) + i += 1 + + if args.export: + radict_export(ret, args) + else: + print("{}".format(json.dumps(ret, indent=4, sort_keys=True))) + + except Exception as e: + print("Problems with radict.py: {}".format(e)) diff --git a/src/language/python/setup.cfg b/src/language/python/setup.cfg new file mode 100644 index 0000000000..96fadd5c34 --- /dev/null +++ b/src/language/python/setup.cfg @@ -0,0 +1,3 @@ +[egg_info] +tag_build = +tag_date = 0 diff --git a/src/language/python/setup.py b/src/language/python/setup.py new file mode 100644 index 0000000000..8595faf4f9 --- /dev/null +++ b/src/language/python/setup.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# vi:ts=4:et + +# +# Python bindings for libfreeradius +# +# @copyright Network RADIUS SAS(legal@networkradius.com) +# @author Jorge Pereira +# + +"""Setup script for the PyFr module distribution.""" +PACKAGE = "pyfr" +PY_PACKAGE = "fr" +VERSION = "0.0.1" + +import os +from distutils.core import setup, Extension + +# +# Remove it only for now. +# +STRIP_CFLAGS = [ + "-Werror" +] + +def fr_get_env(_env, _strip=[]): + ret = [] + _e = os.getenv(_env) + if _e: + _e.replace("src/", "../../../src/") # Fix path + for _k in _e.split(): + if _k not in _strip: + ret.append(_k) + return ret + +BUILD_DIR = ''.join(fr_get_env("top_builddir")) + "/build" +CFLAGS = fr_get_env("CFLAGS", STRIP_CFLAGS) +CPPFLAGS = fr_get_env("CPPFLAGS", STRIP_CFLAGS) +LIBS = fr_get_env("LIBS") +LDFLAGS = fr_get_env("LDFLAGS") +LDFLAGS += [ + "-L{}/lib/local/.libs/".format(BUILD_DIR), # Hardcode just for now + "-lfreeradius-radius", + "-lfreeradius-internal", + "-lfreeradius-util" +] + +# TODO: It should be based in some 'version.h.in' +CFLAGS.append("-DMODULE_NAME=\"{}\"".format(PACKAGE)) +CFLAGS.append("-DPYFR_VERSION={}".format(VERSION)) +CFLAGS.append("-DPYFR_VERSION_MAJOR={}".format(VERSION.split('.')[0])) +CFLAGS.append("-DPYFR_VERSION_MINOR={}".format(VERSION.split('.')[1])) +CFLAGS.append("-DPYFR_VERSION_INCRM={}".format(VERSION.split('.')[2])) + +if os.getenv("VERBOSE"): + print("########## Debug") + print("CFLAGS = '{}'".format(' '.join(CFLAGS))) + print("CPPFLAGS = '{}'".format(' '.join(CPPFLAGS))) + print("LDFLAGS = '{}'".format(' '.join(LDFLAGS))) + print("LIBS = '{}'".format(' '.join(LIBS))) + +if __name__ == "__main__": + ext = Extension(name = PACKAGE, + sources = [ + "src/module.c", + "src/util.c", + "src/radius.c" + ], + include_dirs = [ + "../../../", + "../../" + ], + libraries = [ + "freeradius-util", + "freeradius-radius", + "freeradius-internal" + ], + extra_compile_args = CFLAGS + CPPFLAGS, + extra_link_args = LIBS + LDFLAGS, + undef_macros=['NDEBUG'] # The FreeRADIUS API should decided that. + ) + + setup_args = dict( + name=PACKAGE, + version=VERSION, + description = 'PyFr -- A Python Interface To The libfreeradius libraries', + python_requires='>=3.10', + platforms = "All", + ext_modules = [ext], + ) + setup(**setup_args) diff --git a/src/language/python/src/module.c b/src/language/python/src/module.c new file mode 100644 index 0000000000..e2bd6c294c --- /dev/null +++ b/src/language/python/src/module.c @@ -0,0 +1,343 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/module.c + * @brief Python bindings for major FreeRADIUS libraries + * + * @copyright Network RADIUS SAS(legal@networkradius.com) + * @author 2023 Jorge Pereira (jpereira@freeradius.org) + */ +RCSID("$Id$") + +#include "pyfr.h" +#include "src/version.h" +#include "src/util.h" // pyfr.Util.* ~> libfreeradius-util* +#include "src/radius.h" // pyfr.Radius.* ~> libfreeradius-radius* + +PYFR_INTERNAL char const *pyfr_version = STRINGIFY(PYFR_VERSION_MAJOR) "." STRINGIFY(PYFR_VERSION_MINOR) "." STRINGIFY(PYFR_VERSION_INCRM); +PYFR_INTERNAL char const *pyfr_version_build = PYFR_VERSION_BUILD(); + +PYFR_INTERNAL char const *libfreeradius_version = STRINGIFY(RADIUSD_VERSION_MAJOR) "." STRINGIFY(RADIUSD_VERSION_MINOR) "." STRINGIFY(RADIUSD_VERSION_INCRM); +PYFR_INTERNAL char const *libfreeradius_version_build = RADIUSD_VERSION_BUILD("libfreeradius"); + +/* Singleton settings */ +pyfr_mod_state_t *pyfr_get_mod_state(void) { + static pyfr_mod_state_t _state = { 0 }; + + return &_state; +} + +PyObject *pyfr_ErrorObject = NULL; + +PYFR_INTERNAL int pyfr_register_consts(PyObject *m) +{ + struct pyfr_consts_s { + const char *key, *var; + } pyfr_consts[] = { + { "LOGDIR", LOGDIR }, + { "LIBDIR", LIBDIR }, + { "RADDBDIR", RADDBDIR }, + { "RUNDIR", RUNDIR }, + { "SBINDIR", SBINDIR }, + { "RADIR", RADIR }, + { "DICTDIR", DICTDIR }, + { NULL, NULL } + }; + uint8_t i = 0; + + PyModule_AddStringConstant(m, "version", pyfr_version); + PyModule_AddStringConstant(m, "version_build", pyfr_version_build); + PyModule_AddStringConstant(m, "libfreeradius_version", libfreeradius_version); + PyModule_AddStringConstant(m, "libfreeradius_version_build", libfreeradius_version_build); + + for (; pyfr_consts[i].key; i++) PyModule_AddStringConstant(m, pyfr_consts[i].key, pyfr_consts[i].var); + + return 1; +} + +/* Bootstrap all modules */ +PYFR_INTERNAL int pyfr_register_modules(PyObject *m) +{ + uint8_t i = 0; + struct pyfr_mods_s { + char const *name; + PyTypeObject *(*mod_register)(void); + char const *err_name; + PyObject **err_obj; + } pyfr_mods[] = { + { "Util", pyfr_util_register, "pyfr_ErrorUtil", &pyfr_ErrorUtil }, + { "Radius", pyfr_radius_register, "pyfr_ErrorRadius", &pyfr_ErrorRadius }, + { NULL } + }; + + for (; pyfr_mods[i].name; i++) { + PyTypeObject *type_obj; + char *err_name; + + /* Setup the Module */ + type_obj = pyfr_mods[i].mod_register(); + if (!type_obj) return 0; + + if (PyType_Ready(type_obj) < 0) return 0; + Py_INCREF(type_obj); + PyModule_AddObject(m, pyfr_mods[i].name, (PyObject *)type_obj); + + /* Setup the Exception */ + err_name = talloc_asprintf(NULL, "pyfr.%s", pyfr_mods[i].err_name); + *pyfr_mods[i].err_obj = PyErr_NewException(err_name, NULL, NULL); + Py_INCREF(*pyfr_mods[i].err_obj); + PyModule_AddObject(m, pyfr_mods[i].err_name, *pyfr_mods[i].err_obj); + + talloc_free(err_name); + } + + return 1; +} + +PYFR_INTERNAL int pyfr_bootstrap_libfreeradius(UNUSED PyObject *m) +{ + pyfr_mod_state_t *s = pyfr_get_mod_state(); + + /* + * Must be called first, so the handler is called last + */ + fr_atexit_global_setup(); + +#ifndef NDEBUG + s->autofree = talloc_autofree_context(); + + if (fr_fault_setup(s->autofree, getenv("PANIC_ACTION"), "pyfr") < 0) { + PyErr_SetString(PyExc_RuntimeError, fr_strerror()); + goto error; + } +#endif + + talloc_set_log_stderr(); + + /* + * Always log to stdout + */ + // TODO: these attributes should be a Python const + default_log.dst = L_DST_STDOUT; + default_log.fd = STDOUT_FILENO; + default_log.print_level = false; + + if (fr_log_init_legacy(&default_log, false) < 0) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorObject); + goto error; + } + + /* + * Mismatch between the binary and the libraries it depends on + */ + if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorObject); + goto error; + } + + fr_strerror_clear(); /* Clear the error buffer */ + + return 1; + +error: + if (talloc_free(s->autofree) < 0) fr_perror("pyfr"); + + s->autofree = NULL; + + /* + * Ensure our atexit handlers run before any other + * atexit handlers registered by third party libraries. + */ + fr_atexit_global_trigger_all(); + + return 0; +} + +PYFR_INTERNAL PyObject *pyfr_PyFR(PyObject *self, PyObject *args, PyObject *kwargs) +{ + pyfr_mod_state_t *state = pyfr_get_mod_state(); + const char * const keywords[] = { "raddb_dir", "dict_dir", "lib_dir", "debug_lvl", NULL}; + char *raddb_dir = NULL, *dict_dir = NULL, *lib_dir = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|sssi", UNCONST(char **, keywords), &raddb_dir, &dict_dir, &lib_dir, &fr_debug_lvl)) return NULL; + + DEBUG2("raddb_dir='%s', dict_dir='%s', lib_dir='%s'", raddb_dir, dict_dir, lib_dir); + +#define _SET_VAR(var, dflt) state->var = talloc_strdup(NULL, (var && strlen(var)) > 0 ? var : dflt) + + _SET_VAR(raddb_dir, RADDBDIR); + _SET_VAR(dict_dir, DICTDIR); + _SET_VAR(lib_dir, LIBDIR); + + /* + * It's easier having two sets of flags to set the + * verbosity of library calls and the verbosity of + * library. + */ + fr_debug_lvl = 0; + fr_log_fp = stdout; // TODO: Move to some API settings. + + return (PyObject *)self; +} + +PYFR_INTERNAL PyObject *pyfr_set_raddb_dir(PyObject *self, PyObject *args) +{ + char *raddb_dir = NULL; + + if (PyArg_ParseTuple(args, "s", &raddb_dir)) { + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + DEBUG3("raddb_dir='%s'", raddb_dir); + + state->raddb_dir = talloc_strdup(NULL, (raddb_dir && strlen(raddb_dir) > 0) ? raddb_dir : RADDBDIR); + } + + return (PyObject *)self; +} + +PYFR_INTERNAL PyObject *pyfr_set_dict_dir(PyObject *self, PyObject *args) +{ + char *dict_dir = NULL; + + if (PyArg_ParseTuple(args, "s", &dict_dir)) { + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + DEBUG3("dict_dir='%s'", dict_dir); + + state->dict_dir = talloc_strdup(NULL, (dict_dir && strlen(dict_dir) > 0) ? dict_dir : DICTDIR); + } + + return (PyObject *)self; +} + +PYFR_INTERNAL PyObject *pyfr_set_lib_dir(PyObject *self, PyObject *args) +{ + char *lib_dir = NULL; + + if (PyArg_ParseTuple(args, "s", &lib_dir)) { + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + DEBUG3("lib_dir='%s'", lib_dir); + + state->lib_dir = talloc_strdup(NULL, (lib_dir && strlen(lib_dir) > 0) ? lib_dir : LIBDIR); + } + + return (PyObject *)self; +} + +PYFR_INTERNAL PyObject *pyfr_set_debug_level(PyObject *self, PyObject *args) +{ + if (PyArg_ParseTuple(args, "i", &fr_debug_lvl)) DEBUG3("fr_debug_lvl='%d'", fr_debug_lvl); + + return (PyObject *)self; +} + +PYFR_INTERNAL PyObject *pyfr_version_info(UNUSED PyObject *self, UNUSED PyObject *args) +{ + PyObject *ret = NULL; + PyObject *tmp; + + ret = PyTuple_New((Py_ssize_t)4); /* (pyfr_version, git_hash, arch, built) */ + if (ret == NULL) goto error; + +#define SET(i, v) \ + tmp = (v); if (tmp == NULL) goto error; PyTuple_SET_ITEM(ret, i, tmp) + SET(0, PyUnicode_FromString(pyfr_version)); + SET(1, PyUnicode_FromString(PYFR_VERSION_COMMIT_STRING)); + SET(2, PyUnicode_FromString(HOSTINFO)); + SET(3, PyUnicode_FromString(_PYFR_VERSION_BUILD_TIMESTAMP)); +#undef SET + + return ret; + +error: + Py_XDECREF(ret); + return NULL; +} + +PYFR_INTERNAL void pyfr_mod_free(UNUSED void *unused) { + +#ifndef NDEBUG + talloc_free(pyfr_get_mod_state()->autofree); +#endif + + /* + * Ensure our atexit handlers run before any other + * atexit handlers registered by third party libraries. + */ + fr_atexit_global_trigger_all(); +} + +/* List of functions defined in this module */ +PYFR_INTERNAL PyMethodDef pyfr_methods[] = { + { "PyFR", (PyCFunction)pyfr_PyFR, METH_VARARGS | METH_KEYWORDS, NULL }, + { "set_raddb_dir", (PyCFunction)pyfr_set_raddb_dir, METH_VARARGS, NULL }, + { "set_dict_dir", (PyCFunction)pyfr_set_dict_dir, METH_VARARGS, NULL }, + { "set_lib_dir", (PyCFunction)pyfr_set_lib_dir, METH_VARARGS, NULL }, + { "set_debug_level", (PyCFunction)pyfr_set_debug_level, METH_VARARGS, NULL }, + { "get_version_info", (PyCFunction)pyfr_version_info, METH_NOARGS, NULL }, + { NULL, NULL, 0, NULL } +}; + +PYFR_INTERNAL PyModuleDef pyfr_module = { + PyModuleDef_HEAD_INIT, + .m_name = "pyfr", + .m_doc = "Python bindings for miscellaneous FreeRADIUS functions.", + .m_size = -1, + .m_methods = pyfr_methods, + .m_traverse = NULL, + .m_clear = NULL, + .m_free = pyfr_mod_free +}; + +PyMODINIT_FUNC PyInit_pyfr(void); +PyMODINIT_FUNC PyInit_pyfr(void) +{ + PyObject *m; + + m = PyModule_Create(&pyfr_module); + if (!m) return NULL; + + /* Add error object to the module */ + pyfr_ErrorObject = PyErr_NewException("pyfr.ErrorObject", PyExc_RuntimeError, NULL); + if (pyfr_ErrorObject) { + Py_INCREF(pyfr_ErrorObject); + PyModule_AddObject(m, "ErrorObject", pyfr_ErrorObject); + } + + /* Load some consts like version and default paths */ + if (!pyfr_register_consts(m)) goto error; + + /* then, let's call everything needed by libfreeradius* */ + if (!pyfr_bootstrap_libfreeradius(m)) goto error; + + /* Bootstrap all modules */ + if (!pyfr_register_modules(m)) goto error; + + return m; + +error: + if (!PyErr_Occurred()) PyErr_SetString(PyExc_ImportError, "pyfr module load failed"); + + Py_XDECREF(pyfr_ErrorObject); + Py_CLEAR(pyfr_ErrorObject); + Py_DECREF(m); + + return NULL; +} diff --git a/src/language/python/src/pyfr.h b/src/language/python/src/pyfr.h new file mode 100644 index 0000000000..75a058242c --- /dev/null +++ b/src/language/python/src/pyfr.h @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/pyfr.h + * @brief Python bindings for major FreeRADIUS libraries + * + * @copyright Network RADIUS SAS(legal@networkradius.com) + * @author 2023 Jorge Pereira (jpereira@freeradius.org) + */ +RCSIDH(pyfr_h, "$Id$") + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#include + +#define PYFR_TYPE_FLAGS Py_TPFLAGS_HAVE_GC +#define PYFR_SINGLE_FILE +#define PY_SSIZE_T_CLEAN +#include +#include +#include + +#if !(PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 10) /* At least 3.10.x */ + #error "We expect Python >= 3.10.x" +#endif + +#if defined(PYFR_SINGLE_FILE) +# define PYFR_INTERNAL static +#else +# define PYFR_INTERNAL +#endif + +typedef struct { + PyObject_HEAD + PyObject *error; + bool util_loaded; + bool radius_loaded; + + TALLOC_CTX *autofree; + + char *raddb_dir; //!< Path to raddb directory + char *dict_dir; //!< The location for loading dictionaries + char *lib_dir; //!< The location for loading libraries +} pyfr_mod_state_t; + +pyfr_mod_state_t *pyfr_get_mod_state(void); + +DIAG_OFF(unused-macros) +#define DEBUG(fmt, ...) if (fr_log_fp && (fr_debug_lvl > 1)) fr_fprintf(fr_log_fp , "** DEBUG: pyfr: %s:%d %s(): "fmt "\n", __FILE__, __LINE__, __func__, ## __VA_ARGS__) +#define DEBUG2(fmt, ...) if (fr_log_fp && (fr_debug_lvl > 2)) fr_fprintf(fr_log_fp , "** DEBUG2: pyfr: %s:%d %s(): "fmt "\n", __FILE__, __LINE__, __func__, ## __VA_ARGS__) +#define DEBUG3(fmt, ...) if (fr_log_fp && (fr_debug_lvl > 3)) fr_fprintf(fr_log_fp , "** DEBUG3: pyfr: %s:%d %s(): "fmt "\n", __FILE__, __LINE__, __func__, ## __VA_ARGS__) +#define INFO(fmt, ...) if (fr_log_fp && (fr_debug_lvl > 0)) fr_fprintf(fr_log_fp , "** INFO: pyfr: %s:%d %s(): "fmt "\n", __FILE__, __LINE__, __func__, ## __VA_ARGS__) +DIAG_ON(unused-macros) + +#ifndef NDEBUG +# define pyfr_ErrorObject_as_strerror(pyErrorObj) PyErr_Format(pyErrorObj, "%s:%d %s(): %s", __FILE__, __LINE__, __func__, fr_strerror()) +#else +# define pyfr_ErrorObject_as_strerror(pyErrorObj) PyErr_SetString(pyErrorObj, fr_strerror()) +#endif + +#ifdef __cplusplus +} +#endif diff --git a/src/language/python/src/radius.c b/src/language/python/src/radius.c new file mode 100644 index 0000000000..5bc13d37ed --- /dev/null +++ b/src/language/python/src/radius.c @@ -0,0 +1,537 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/radius.c + * @brief Python bindings for major FreeRADIUS libraries + * + * @copyright Network RADIUS SAS(legal@networkradius.com) + * @author 2023 Jorge Pereira (jpereira@freeradius.org) + */ + +RCSID("$Id$") + +#include "src/pyfr.h" +#include "src/radius.h" + +#include +#include +#include +#include + +extern fr_dict_t const *dict_freeradius; +extern fr_dict_t const *dict_radius; + +PyObject *pyfr_ErrorRadius = NULL; + +PYFR_INTERNAL int pyfr_radius_init(UNUSED PyObject *self, UNUSED PyObject *args, UNUSED PyObject *kwds) +{ + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + if (state->radius_loaded) return 0; + + DEBUG3("Initialising libfreeradius-radius"); + + if (fr_radius_init() < 0) { + PyErr_Format(pyfr_ErrorRadius, "fr_radius_init() Failed: %s", fr_strerror()); + goto error; + } + + state->radius_loaded = true; + + return 0; + +error: + return -1; +} + +PYFR_INTERNAL PyObject *pyfr_radius_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + const char * const keywords[] = { "auth_host", "auth_port", NULL}; + char const *auth_host = NULL, *auth_port = NULL; + pyfr_radius_ctx_t *ctx; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ss", UNCONST(char **, keywords), &auth_host, &auth_port)) return NULL; + + if (!auth_host) auth_host = "127.0.0.1"; + if (!auth_port) auth_port = "1812"; + + ctx = PyObject_New(pyfr_radius_ctx_t, type); + ctx->auth_host = talloc_strdup(NULL, auth_host); + ctx->auth_port = talloc_strdup(NULL, auth_port); + + return (PyObject *)ctx; +} + +PYFR_INTERNAL void pyfr_radius_dealloc(PyObject *self) +{ + pyfr_radius_ctx_t *ctx = (pyfr_radius_ctx_t *)self; + + fr_radius_free(); + + TALLOC_FREE(ctx->auth_host); + TALLOC_FREE(ctx->auth_port); + + PyObject_Del(ctx); +} + +static void *pyfr_radius_next_encodable(fr_dlist_head_t *list, void *current, void *uctx) +{ + fr_pair_t *vp = current; + fr_dict_t *dict = talloc_get_type_abort(uctx, fr_dict_t); + + while ((vp = fr_dlist_next(list, vp))) { + PAIR_VERIFY(vp); + if ((vp->da->dict == dict) && + (!vp->da->flags.internal || ((vp->da->attr > FR_TAG_BASE) && (vp->da->attr < (FR_TAG_BASE + 0x20))))) { + break; + } + } + + return vp; +} + +PYFR_INTERNAL PyObject *pyfr_radius_encode_pair(UNUSED PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + const char * const keywords[] = { "attrs", "secret", NULL}; + PyObject *data = NULL, *kattrs, *key, *value_list; + Py_ssize_t pos = 0; + fr_pair_t *vp; + fr_pair_list_t tmp_list; + fr_dict_t const *dict = dict_radius; + fr_dcursor_t cursor; + fr_dbuff_t work_dbuff; + char buff[MAX_PACKET_LEN]; + char *ksecret, *secret = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os", UNCONST(char **, keywords), &kattrs, &ksecret)) { + PyErr_SetString(pyfr_ErrorRadius, "Invalid Argument. e.g: attrs={ \"attribute1\": [ \"arg1\" ], \"attribute2\": [ \"arg2\" , ... ] }"); + return NULL; + } + + fr_pair_list_init(&tmp_list); + + /* + * Walk through the Radius.encode_pair(..., attrs={ "attrs": [ "arg" ], ... }, ...) + * parameters and build VPs list + */ + while (PyDict_Next(kattrs, &pos, &key, &value_list)) { + if (!PyList_Check(value_list)) { + PyErr_Format(pyfr_ErrorRadius, "Wrong argument at position %ld, it must be a 'list'. e.g: attrs={\"attribute\": [ \"arg1\", ... ] }", pos); + goto error; + } + + for (int i = 0; i < PyList_Size(value_list); i++) { + char const *lhs, *rhs; + char *lhs_rhs; + + lhs = PyUnicode_AsUTF8(key); + rhs = PyUnicode_AsUTF8(PyList_GetItem(value_list, i)); + lhs_rhs = talloc_asprintf(NULL, "%s=\"%s\"", lhs, rhs); + + DEBUG2("Encode %s", lhs_rhs); + + if (fr_pair_list_afrom_str(NULL, fr_dict_root(dict_radius), lhs_rhs, strlen(lhs_rhs), &tmp_list) != T_EOL) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorRadius); + talloc_free(lhs_rhs); + goto error; + } + + talloc_free(lhs_rhs); + } + } + + /* + * Output may be an error, and we return it if so. + */ + if (fr_pair_list_empty(&tmp_list)) { + PyErr_SetString(pyfr_ErrorRadius, "Empty avp list."); + goto error; + } + + fr_dbuff_init(&work_dbuff, buff, sizeof(buff)); + + /* fr_radius_encode_pair() expects talloced 'secret' parameter */ + secret = talloc_strdup(NULL, ksecret); + + /* + * Loop over the reply attributes for the packet. + */ + fr_pair_dcursor_iter_init(&cursor, &tmp_list, pyfr_radius_next_encodable, dict); + while ((vp = fr_dcursor_current(&cursor))) { + PAIR_VERIFY(vp); + + DEBUG3("Calling fr_radius_encode_pair() for %pP (%s).", vp, fr_type_to_str(vp->da->type)); + + /* + * Encode an individual VP + */ + if (fr_radius_encode_pair(&work_dbuff, &cursor, &(fr_radius_ctx_t){ .secret = secret }) < 0) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorRadius); + goto error; + } + } + + FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "%s encoded packet", __FUNCTION__); + + data = Py_BuildValue("y#", fr_dbuff_start(&work_dbuff),fr_dbuff_used(&work_dbuff)); + if (!data) { + PyErr_SetString(pyfr_ErrorRadius, "Py_BuildValue() failed."); + goto error; + } + +error: + TALLOC_FREE(secret); + + /* clean up and return result */ + fr_pair_list_free(&tmp_list); + + return data; +} + +PYFR_INTERNAL PyObject *pyfr_radius_decode_pair(UNUSED PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *attrs = NULL; + fr_pair_t *vp; + fr_pair_list_t tmp_list; + fr_dcursor_t cursor; + const char * const keywords[] = { "data", "secret", NULL}; + char *ksecret; + uint8_t *kdata, *ptr; + size_t kdata_len, ptr_len, my_len; + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y#s", UNCONST(char **, keywords), &kdata, &kdata_len, &ksecret)) { + PyErr_SetString(pyfr_ErrorRadius, "Invalid parameter, expecting packet payload."); + return NULL; + } + + FR_PROTO_HEX_DUMP(kdata, kdata_len, "%s decode packet", __FUNCTION__); + + ptr = kdata; + ptr_len = kdata_len; + + fr_pair_list_init(&tmp_list); + + /* + * Loop over the attributes, decoding them into VPs. + */ + while (ptr_len > 0) { + my_len = fr_radius_decode_pair(state->autofree, &tmp_list, ptr, ptr_len, &(fr_radius_ctx_t){ .secret = ksecret, .end = (kdata + kdata_len) }); + if (my_len < 0) { + PyErr_Format(pyfr_ErrorRadius, "fr_radius_decode_pair() returned %ld. (%s)", my_len, fr_strerror()); + goto error; + } + + /* + * If my_len is larger than the room in the packet, + * all kinds of bad things happen. + */ + if (!fr_cond_assert(my_len <= ptr_len)) goto error; + + ptr += my_len; + ptr_len -= my_len; + } + + if (fr_pair_list_num_elements(&tmp_list) < 1) { + PyErr_SetString(pyfr_ErrorRadius, "Failed decoding packet"); + goto error; + } + + attrs = PyDict_New(); + for (vp = fr_pair_dcursor_init(&cursor, &tmp_list); + vp; + vp = fr_dcursor_next(&cursor)) { + PyObject *value_list; + char lhs[64], rhs[128]; + + PAIR_VERIFY(vp); + + DEBUG3("Decoding %pP", vp); + + fr_dict_attr_oid_print(&FR_SBUFF_OUT(lhs, sizeof(lhs)), NULL, vp->da, false); + fr_pair_print_value_quoted(&FR_SBUFF_OUT(rhs, sizeof(rhs)), vp, T_BARE_WORD); + + /* the RHS already exists? then, append it */ + value_list = PyDict_GetItemString(attrs, lhs); + if (!value_list) value_list = PyList_New(0); + + PyList_Append(value_list, PyUnicode_FromString(rhs)); + PyDict_SetItemString(attrs, lhs, value_list); + } + +error: + /* clean up and return result */ + fr_pair_list_free(&tmp_list); + + return attrs; +} + +PYFR_INTERNAL PyObject *pyfr_radius_encode_packet(UNUSED PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + const char * const keywords[] = { "attrs", "id", "secret", NULL}; + uint8_t kpacket_id = 0; + char *ksecret = NULL, *secret = NULL; + Py_ssize_t pos = 0, i =0; + PyObject *data = NULL, *kattrs, *key, *value_list; + fr_pair_t *vp; + fr_pair_list_t tmp_list; + uint8_t buff[MAX_PACKET_LEN]; + ssize_t slen; + char *lhs_rhs; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OBs", UNCONST(char **, keywords), &kattrs, &kpacket_id, &ksecret)) return NULL; + + fr_pair_list_init(&tmp_list); + + /* + * Walk through the Radius.encode_packet(..., attrs={ "attrs": [ "arg" ], ... }, ...) + * parameters and build VPs list + */ + while (PyDict_Next(kattrs, &pos, &key, &value_list)) { + if (!PyList_Check(value_list)) { + PyErr_SetString(pyfr_ErrorRadius, "Wrong argument, it must be a 'list'. e.g: \"attribute\": [ \"arg1\", ... ]"); + goto error; + } + + for (i = 0; i < PyList_Size(value_list); i++) { + char const *lhs, *rhs; + + lhs = PyUnicode_AsUTF8(key); + rhs = PyUnicode_AsUTF8(PyList_GetItem(value_list, i)); + lhs_rhs = talloc_asprintf(NULL, "%s=\"%s\"", lhs, rhs); + + DEBUG2("Encode %s", lhs_rhs); + + if (fr_pair_list_afrom_str(NULL, fr_dict_root(dict_radius), lhs_rhs, strlen(lhs_rhs), &tmp_list) != T_EOL) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorRadius); + talloc_free(lhs_rhs); + goto error; + } + + talloc_free(lhs_rhs); + } + } + + /* + * Output may be an error, and we return it if so. + */ + if (fr_pair_list_empty(&tmp_list)) { + PyErr_SetString(pyfr_ErrorRadius, "Empty avp list"); + goto error; + } + + /* We can't go without Packet-Type */ + vp = fr_pair_find_by_child_num(&tmp_list, NULL, fr_dict_root(dict_radius), FR_PACKET_TYPE); + if (!vp) { + PyErr_SetString(pyfr_ErrorRadius, "We can not go without 'Packet-Type' attribute."); + goto error; + } + + /* fr_radius_encode_pair() expects talloced 'secret' parameter */ + secret = talloc_strdup(NULL, ksecret); + + slen = fr_radius_encode(buff, sizeof(buff), NULL, secret, strlen(secret), vp->vp_uint32, kpacket_id, &tmp_list); + if (slen < 0) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorRadius); + goto error; + } + + FR_PROTO_HEX_DUMP(buff, slen, "%s encoded data", __FUNCTION__); + + data = Py_BuildValue("y#", buff, slen); + if (!data) { + PyErr_SetString(pyfr_ErrorRadius, "Py_BuildValue() failed."); + goto error; + } + +error: + /* clean up and return result */ + + TALLOC_FREE(secret); + + fr_pair_list_free(&tmp_list); + return data; +} + +PYFR_INTERNAL PyObject *pyfr_radius_decode_packet(UNUSED PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *ret = NULL, *attrs; + fr_pair_t *vp; + fr_pair_list_t tmp_list; + fr_dcursor_t cursor; + const char * const keywords[] = { "data", "secret", NULL}; + char *ksecret, *secret; + uint8_t const *kdata; + size_t kdata_len; + uint8_t packet_id = 0; + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + /* get one argument as an iterator */ + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y#s", UNCONST(char **, keywords), &kdata, &kdata_len, &ksecret)) return NULL; + + FR_PROTO_HEX_DUMP(kdata, kdata_len, "%s decode packet", __FUNCTION__); + + fr_pair_list_init(&tmp_list); + + secret = talloc_strdup(NULL, ksecret); /* Internally the fr_radius_decode_tunnel_password() expects a talloc's secret string. */ + if (fr_radius_decode(state->autofree, &tmp_list, kdata, kdata_len, NULL, secret, talloc_array_length(secret) - 1) < 0) { + PyErr_SetString(pyfr_ErrorRadius, "Failed decoding packet"); + goto error; + } + + if (fr_pair_list_num_elements(&tmp_list) < 1) { + PyErr_SetString(pyfr_ErrorRadius, "Failed decoding packet"); + goto error; + } + + /* Add the virtual Packet-Type attribute */ + vp = fr_pair_afrom_child_num(NULL, fr_dict_root(dict_radius), FR_PACKET_TYPE); + if (!vp) { + PyErr_SetString(pyfr_ErrorRadius, "Failed fr_pair_afrom_child_num(..., ..., FR_PACKET_TYPE)"); + goto error; + } + vp->vp_uint32 = kdata[0]; + fr_pair_prepend(&tmp_list, vp); + + /* Set packet id */ + packet_id = kdata[1]; + + /* let's walkthrough the packets */ + attrs = PyDict_New(); + for (vp = fr_pair_dcursor_init(&cursor, &tmp_list); + vp; + vp = fr_dcursor_next(&cursor)) { + PyObject *value_list; + char lhs[64], rhs[128]; + + PAIR_VERIFY(vp); + + DEBUG2("Decoding %pP", vp); + + fr_dict_attr_oid_print(&FR_SBUFF_OUT(lhs, sizeof(lhs)), NULL, vp->da, false); + fr_pair_print_value_quoted(&FR_SBUFF_OUT(rhs, sizeof(rhs)), vp, T_BARE_WORD); + + /* the RHS already exists? then, append it */ + value_list = PyDict_GetItemString(attrs, lhs); + if (!value_list) value_list = PyList_New(0); + + PyList_Append(value_list, PyUnicode_FromString(rhs)); + PyDict_SetItemString(attrs, lhs, value_list); + } + + /* then, built the return */ + ret = Py_BuildValue("i,O", packet_id, attrs); + if (!ret) { + PyErr_SetString(pyfr_ErrorRadius, "Py_BuildValue() failed."); + goto error; + } + +error: + TALLOC_FREE(secret); + + /* clean up and return result */ + fr_pair_list_free(&tmp_list); + + return ret; +} + +PYFR_INTERNAL PyMemberDef pyfr_radius_members[] = { + {"host", T_STRING, offsetof(pyfr_radius_ctx_t, auth_host), 0, "RADIUS host"}, + {"port", T_STRING, offsetof(pyfr_radius_ctx_t, auth_port), 0, "RADIUS port"}, + { NULL } /* Sentinel */ +}; + +/* List of functions defined in this module */ +PYFR_INTERNAL PyMethodDef pyfr_radius_methods[] = { + { + "encode_pair", (PyCFunction)pyfr_radius_encode_pair, (METH_VARARGS | METH_KEYWORDS), + "Encode a data structure into a RADIUS attribute." + "This is the main entry point into the encoder. It sets up the encoder array" + "we use for tracking our TLV/VSA nesting and then calls the appropriate" + "dispatch function." + }, + + { + "decode_pair", (PyCFunction)pyfr_radius_decode_pair, (METH_VARARGS | METH_KEYWORDS), + "Decode a raw RADIUS packet into VPs." + }, + + { + "encode_packet", (PyCFunction)pyfr_radius_encode_packet, (METH_VARARGS | METH_KEYWORDS), + "Encode a data structure into a RADIUS attribute and reply as a dict() table." + "This is the main entry point into the encoder. It sets up the encoder array" + "we use for tracking our TLV/VSA nesting and then calls the appropriate" + "dispatch function." + }, + + { + "decode_packet", (PyCFunction)pyfr_radius_decode_packet, (METH_VARARGS | METH_KEYWORDS), + "Decode a raw RADIUS packet into VPs." + "It returns: packet_id, attrs" + }, + + { NULL } +}; + +PYFR_INTERNAL PyTypeObject pyfr_radius_ctx_types = { + PyVarObject_HEAD_INIT(NULL, 0) "pyfr.Radius", /* tp_name */ + sizeof(pyfr_radius_ctx_t), /* tp_basicsize */ + 0, /* tp_itemsize */ + pyfr_radius_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags*/ + "Object to use libfreeradius-radius library.", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + pyfr_radius_methods, /* tp_methods */ + pyfr_radius_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + pyfr_radius_init, /* tp_init */ + 0, /* tp_alloc */ + pyfr_radius_new, /* tp_new */ +}; + +PyTypeObject *pyfr_radius_register(void) +{ + DEBUG2("Loading pyfr.Radius"); + + return &pyfr_radius_ctx_types; +} diff --git a/src/language/python/src/radius.h b/src/language/python/src/radius.h new file mode 100644 index 0000000000..074bafc954 --- /dev/null +++ b/src/language/python/src/radius.h @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/radius.h + * @brief Python bindings for major FreeRADIUS libraries + * + * @copyright Network RADIUS SAS(legal@networkradius.com) + * @author 2023 Jorge Pereira (jpereira@freeradius.org) + */ + +RCSIDH(pyfr_radius_h, "$Id$") + +#ifdef __cplusplus +extern "C" { +#endif + +#define PY_SSIZE_T_CLEAN +#include +#include + +extern PyObject *pyfr_ErrorRadius; + +typedef struct { + PyObject_HEAD + char *auth_host; //!< auth host + char *auth_port; //!< auth host port +} pyfr_radius_ctx_t; + +PyTypeObject *pyfr_radius_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/language/python/src/util.c b/src/language/python/src/util.c new file mode 100644 index 0000000000..3a526b4e8c --- /dev/null +++ b/src/language/python/src/util.c @@ -0,0 +1,231 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/util.c + * @brief Python bindings for major FreeRADIUS libraries + * + * @copyright Network RADIUS SAS(legal@networkradius.com) + * @author 2023 Jorge Pereira (jpereira@freeradius.org) + */ + +RCSID("$Id$") + +#include "src/pyfr.h" +#include "src/util.h" + +#include +#include +#include +#include + +PyObject *pyfr_ErrorUtil = NULL; + +fr_dict_t const *dict_freeradius; +fr_dict_t const *dict_radius; + +extern fr_dict_autoload_t pyfr_dict[]; +fr_dict_autoload_t pyfr_dict[] = { + { .out = &dict_freeradius, .proto = "freeradius" }, + { .out = &dict_radius, .proto = "radius" }, + { NULL } +}; + +PYFR_INTERNAL int pyfr_util_init(UNUSED PyObject *self, UNUSED PyObject *args, UNUSED PyObject *kwds) +{ + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + if (state->util_loaded) return 0; + + DEBUG3("Initialising libfreeradius-util"); + + /* + * Initialize the DL infrastructure, which is used by the + * config file parser. + */ + if (state->lib_dir && dl_search_global_path_set(state->lib_dir) < 0) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorUtil); + goto error; + } + + /* Load the dictionary */ + if (!fr_dict_global_ctx_init(NULL, true, state->dict_dir)) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorUtil); + goto error; + } + + if (fr_dict_autoload(pyfr_dict) < 0) { + pyfr_ErrorObject_as_strerror(pyfr_ErrorUtil); + goto error; + } + + if (fr_dict_read(fr_dict_unconst(dict_freeradius), state->raddb_dir, FR_DICTIONARY_FILE) == -1) { + fr_log_perror(&default_log, L_ERR, __FILE__, __LINE__, NULL, "fr_dict_read() Failed to initialize the dictionaries"); + PyErr_Format(pyfr_ErrorUtil, "fr_dict_read() Failed initialising the dictionaries"); + goto error; + } + + if (fr_dict_read(fr_dict_unconst(dict_radius), state->raddb_dir, FR_DICTIONARY_FILE) == -1) { + fr_log_perror(&default_log, L_ERR, __FILE__, __LINE__, NULL, "fr_dict_read() Failed to initialize the dictionaries"); + PyErr_Format(pyfr_ErrorUtil, "fr_dict_read() Failed initialising the dictionaries"); + goto error; + } + + state->util_loaded = true; + + return 1; + +error: + return -1; +} + +PYFR_INTERNAL PyObject *pyfr_util_new(PyTypeObject *type, UNUSED PyObject *args, UNUSED PyObject *kwargs) +{ + pyfr_util_ctx_t *ctx = PyObject_New(pyfr_util_ctx_t, type); + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + DEBUG2("raddb_dir='%s', dict_dir='%s', lib_dir='%s'", state->raddb_dir, state->dict_dir, state->lib_dir); + + return (PyObject *)ctx; +} + +PYFR_INTERNAL void pyfr_util_dealloc(PyObject *self) +{ + pyfr_util_ctx_t *ctx = (pyfr_util_ctx_t *)self; + + if (fr_dict_autofree(pyfr_dict) < 0) pyfr_ErrorObject_as_strerror(pyfr_ErrorUtil); + + PyObject_Del(ctx); +} + +PYFR_INTERNAL PyObject *pyfr_util_dict_attr_by_oid(UNUSED PyObject *self, PyObject *args) +{ + PyObject *obj; + const char *oid; + fr_dict_attr_t const *da; + char flags_str[256]; + char oid_str[512]; + char oid_num[16]; + pyfr_mod_state_t *state = pyfr_get_mod_state(); + + if (!PyArg_ParseTuple(args, "s", &oid)) return NULL; + + DEBUG3("Looking for \"%s\" in dict RADIUS", oid); + + da = fr_dict_attr_by_oid(state->autofree, fr_dict_root(dict_radius), oid); + if (!da) { + PyErr_Format(pyfr_ErrorUtil, "OID '%s' not found", oid); + return NULL; + } + + if (fr_dict_attr_oid_print(&FR_SBUFF_OUT(oid_str, sizeof(oid_str)), NULL, da, false) <= 0) { + PyErr_SetString(pyfr_ErrorUtil, "OID string too long"); + return NULL; + } + + if (fr_dict_attr_oid_print(&FR_SBUFF_OUT(oid_num, sizeof(oid_num)), NULL, da, true) <= 0) { + PyErr_SetString(pyfr_ErrorUtil, "OID string too long"); + return NULL; + } + + fr_dict_attr_flags_print(&FR_SBUFF_OUT(flags_str, sizeof(flags_str)), dict_radius, da->type, &da->flags); + + obj = Py_BuildValue("{s:s, s:s, s:s, s:i, s:s, s:s, s:N, s:N, s:N, s:N, s:N, s:N, s:s}", + "oid.string", oid_str, + "oid.numeric", oid_num, + "name", da->name, + "id", da->attr, + "type", fr_type_to_str(da->type), + "flags", flags_str, + "is_root", PyBool_FromLong(da->flags.is_root), + "is_raw", PyBool_FromLong(da->flags.is_raw), + "is_alias", PyBool_FromLong(da->flags.is_alias), + "is_internal", PyBool_FromLong(da->flags.internal), + "has_value", PyBool_FromLong(da->flags.has_value), + "virtual", PyBool_FromLong(da->flags.virtual), + "parent.type", fr_type_to_str(da->parent->type) + ); + + if (!obj) { + PyErr_SetString(pyfr_ErrorUtil, "Problems in Py_BuildValue()"); + return NULL; + } + + return obj; +} + +PYFR_INTERNAL PyMemberDef pyfr_util_members[] = { + { NULL } /* Sentinel */ +}; + +/* List of functions defined in this module */ +PYFR_INTERNAL PyMethodDef pyfr_util_methods[] = { + { + "dict_attr_by_oid", pyfr_util_dict_attr_by_oid, METH_VARARGS, + "Resolve an attribute using an OID string." + }, + + { NULL } +}; + +PYFR_INTERNAL PyTypeObject pyfr_util_types = { + PyVarObject_HEAD_INIT(NULL, 0) "pyfr.Util", /* tp_name */ + sizeof(pyfr_util_ctx_t), /* tp_basicsize */ + 0, /* tp_itemsize */ + pyfr_util_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags*/ + "Object to use libfreeradius-util library.", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + pyfr_util_methods, /* tp_methods */ + pyfr_util_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + pyfr_util_init, /* tp_init */ + 0, /* tp_alloc */ + pyfr_util_new, /* tp_new */ +}; + +PyTypeObject *pyfr_util_register(void) +{ + DEBUG2("Loading pyfr.Util"); + + return &pyfr_util_types; +} diff --git a/src/language/python/src/util.h b/src/language/python/src/util.h new file mode 100644 index 0000000000..6ac06642ca --- /dev/null +++ b/src/language/python/src/util.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/util.h + * @brief Python bindings for major FreeRADIUS libraries + * + * @copyright Network RADIUS SAS(legal@networkradius.com) + * @author 2023 Jorge Pereira (jpereira@freeradius.org) + */ + +RCSIDH(pyfr_util_h, "$Id$") + +#ifdef __cplusplus +extern "C" { +#endif + +#define PY_SSIZE_T_CLEAN +#include +#include + +extern PyObject *pyfr_ErrorUtil; + +typedef struct { + PyObject_HEAD +} pyfr_util_ctx_t; + +PyTypeObject *pyfr_util_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/language/python/src/version.h b/src/language/python/src/version.h new file mode 100644 index 0000000000..2ba0935115 --- /dev/null +++ b/src/language/python/src/version.h @@ -0,0 +1,92 @@ +#pragma once +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** Version checking functions + * + * @file src/version.h + * @brief Python bindings for major FreeRADIUS libraries + * + * @copyright Network RADIUS SAS(legal@networkradius.com) + * @author 2023 Jorge Pereira (jpereira@freeradius.org) + */ +RCSIDH(src_version_h, "$Id$") + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifndef NDEBUG +# define PYFR_VERSION_DEVELOPER "DEVELOPER BUILD - " +#else +# define PYFR_VERSION_DEVELOPER "" +#endif + +#if !defined(PYFR_VERSION_COMMIT) && defined(RADIUSD_VERSION_COMMIT) +#define PYFR_VERSION_COMMIT RADIUSD_VERSION_COMMIT +#endif + +#ifdef PYFR_VERSION_COMMIT +# define PYFR_VERSION_COMMIT_STRING " (git #" STRINGIFY(PYFR_VERSION_COMMIT) ")" +#else +# define PYFR_VERSION_COMMIT_STRING "" +#endif + +#ifndef ENABLE_REPRODUCIBLE_BUILDS +# define _PYFR_VERSION_BUILD_TIMESTAMP "built on " __DATE__ " at " __TIME__ +# define PYFR_VERSION_BUILD_TIMESTAMP ", "_PYFR_VERSION_BUILD_TIMESTAMP +#else +# define PYFR_VERSION_BUILD_TIMESTAMP "" +#endif + +/** Create a version string for a utility in the suite of FreeRADIUS utilities + * + * @param _x utility name + */ +#define PYFR_VERSION_BUILD() \ + PYFR_VERSION_DEVELOPER \ + "version " \ + STRINGIFY(PYFR_VERSION_MAJOR) "." STRINGIFY(PYFR_VERSION_MINOR) "." STRINGIFY(PYFR_VERSION_INCRM) \ + PYFR_VERSION_COMMIT_STRING \ + ", for host " HOSTINFO \ + PYFR_VERSION_BUILD_TIMESTAMP + +#ifdef WITHOUT_VERSION_CHECK +# define PYFR_MAGIC_NUMBER ((uint64_t) (0xf4ee4ad3f4ee4ad3)) +# define MAGIC_PREFIX(_x) ((uint8_t) 0x00) +# define MAGIC_VERSION(_x) ((uint32_t) 0x00000000) +#else +/* + * Mismatch between debug builds between + * the modules and the server causes all + * kinds of strange issues. + */ +# ifndef NDEBUG +# define MAGIC_PREFIX_DEBUG 01 +# else +# define MAGIC_PREFIX_DEBUG 00 +# endif +# define PYFR_MAGIC_NUMBER ((uint64_t) HEXIFY2(MAGIC_PREFIX_DEBUG, PYFR_VERSION)) +# define MAGIC_PREFIX(_x) ((uint8_t) ((0xff00000000000000 & (_x)) >> 56)) +# define MAGIC_VERSION(_x) ((uint32_t)((0x00ffffff00000000 & (_x)) >> 32)) +# define MAGIC_COMMIT(_x) ((uint32_t)((0x00000000ffffffff & (_x)))) +#endif + +#ifdef __cplusplus +} +#endif diff --git a/src/language/python/tests/run.sh b/src/language/python/tests/run.sh new file mode 100755 index 0000000000..0136825018 --- /dev/null +++ b/src/language/python/tests/run.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +for _f in tests/*.py; do + echo "CALL $_f" + $_f +done diff --git a/src/language/python/tests/test.py b/src/language/python/tests/test.py new file mode 100755 index 0000000000..69f8f45255 --- /dev/null +++ b/src/language/python/tests/test.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# +# Test script for pyfr +# Copyright 2023 The FreeRADIUS server project +# Author: Jorge Pereira (jpereira@freeradius.org) +# + +import argparse +import binascii +import os +import sys +import time +import json +import traceback + +from pprint import pprint + +try: + import pyfr +except pyfr.error as e: + print("ERROR: import pyfr {}".format(e)) + +def load_args(): + """ + Load all parameters from the command line. + """ + parser = argparse.ArgumentParser(description = "test script", + formatter_class = argparse.RawDescriptionHelpFormatter) + parser.add_argument("-v", + dest = "verbose", + help = "Verbose mode. (e.g: -vvv)", + action = 'count', + required = False, + default = 0 + ) + + parser.add_argument("-d", + dest = "raddb_dir", + help = "Set configuration directory (defaults {})".format(pyfr.RADDBDIR), + required = False, + default = pyfr.RADDBDIR + ) + parser.add_argument("-D", + dest = "dict_dir", + help = "Path for 'dictionary' directory. (default: {})".format(pyfr.DICTDIR), + required = False, + default = pyfr.DICTDIR + ) + parser.add_argument("-l", + dest = "lib_dir", + help = "Path for 'libraries' directory. (default: {})".format(pyfr.LIBDIR), + required = False, + default = pyfr.LIBDIR + ) + args = parser.parse_args() + + return args + +print("test.py: ###########################################################") +print("test.py: # Consts") +print("test.py: ###########################################################") + +fr = pyfr.PyFR() +# fr = pyfr.PyFR(raddb_dir=raddb_dir, dict_dir=dict_dir, lib_dir=lib_dir, debug_lvl=10) + +args = load_args() + +fr.set_debug_level(args.verbose+2) +fr.set_lib_dir(args.lib_dir) +fr.set_raddb_dir(args.raddb_dir) +fr.set_dict_dir(args.dict_dir) + +# pprint(vars(fr)) + +print("test.py: ###########################################################") +print("test.py: # pyfr.Util") +print("test.py: ###########################################################") + +try: + u = fr.Util() + r = fr.Radius() + + print() + print("test.py: ###########################################################") + print("test.py: Util.dict_attr_by_oid()") + print("test.py: ###########################################################") + attr = "Vendor-Specific.Alcatel.Client-Primary-DNS" + ret = u.dict_attr_by_oid(attr) + print("test.py: pyfr.Util.dict_attr_by_oid('{}') = {}".format(attr, json.dumps(ret, indent=4, sort_keys=True))) + + print() + print("test.py: ###########################################################") + print("test.py: Radius.encode_pair()") + print("test.py: ###########################################################") + attrs = { + "User-Name": [ "hare", "krishina" ], + "User-Password": [ "jorge" ], + "Vendor-Specific.WiMAX.DNS-Server": [ "::1" ], + "Vendor-Specific.Alcatel.Client-Primary-DNS": [ "8.8.8.8", "8.6.6.6" ] + } + data = r.encode_pair(attrs=attrs, secret="testing123") + print("input: {}".format(attrs)) + print("output: {}".format(binascii.hexlify(data))) + + print() + print("test.py: ###########################################################") + print("test.py: Radius.decode_pair()") + print("test.py: ###########################################################") + data = b'010668617265010a6b72697368696e611a19000060b5341300000000000000000000000000000000011a0c00000be10506080808081a0c00000be1050608060606' + attrs = r.decode_pair(data=binascii.unhexlify(data), secret="testing123") + print("input: {}".format(data)) + print("output: {}".format(attrs)) + + print() + print("test.py: ###########################################################") + print("test.py: Radius.encode_packet()") + print("test.py: ###########################################################") + attrs = { + "Packet-Type": [ "Access-Request" ], + "User-Name": [ "jorge", "pereira" ], + "User-Password": [ "jorge" ], + "Vendor-Specific.WiMAX.DNS-Server": [ "::1" ], + "Vendor-Specific.Alcatel.Client-Primary-DNS": [ "8.8.8.8" ] + } + packet_id = 202 + data = r.encode_packet(attrs=attrs, id=packet_id, secret="testing123") + print("attrs: {}".format(attrs)) + print("packet-id: {}".format(packet_id)) + print("packet: {}".format(binascii.hexlify(data))) + + print() + print("test.py: ###########################################################") + print("test.py: Radius.decode_packet()") + print("test.py: ###########################################################") + data = b'01ca00490000000000000000000000000000000001076a6f7267650109706572656972611a19000060b5341300000000000000000000000000000000011a0c00000be1050608080808' + packet_id, attrs = r.decode_packet(data=binascii.unhexlify(data), secret="testing123") + print("attrs: {}".format(attrs)) + print("packet-id: {}".format(packet_id)) + print("packet: {}".format(data)) + +except Exception as e: + print("test.py: Problems with: {}".format(e)) + traceback.print_exc() + + +# print("###########################################################") +# print("# pyfr.Radius") +# print("###########################################################") +# try: +# arg = "bar" +# radius = fr.Radius(auth_host="localhost", auth_port="1812") +# ret = radius.foo("tapioca") +# print("pyfr.radius.foo('{}') = {}".format(arg, json.dumps(ret, indent=4, sort_keys=True))) +# except Exception as e: +# print("Problems with pyfr.radius.foo(): {}".format(e)) +# traceback.print_exc() \ No newline at end of file diff --git a/src/language/python/tests/version.py b/src/language/python/tests/version.py new file mode 100755 index 0000000000..7abaccc373 --- /dev/null +++ b/src/language/python/tests/version.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# +# Test script for pyfr +# Copyright 2023 The FreeRADIUS server project +# Author: Jorge Pereira (jpereira@freeradius.org) +# + +import pyfr + +print("pyfr.version: {}".format(pyfr.version)) +print("pyfr.version_build: {}".format(pyfr.version_build)) +print("pyfr.libfreeradius_version: {}".format(pyfr.libfreeradius_version)) +print("pyfr.libfreeradius_version_build: {}".format(pyfr.libfreeradius_version_build)) +print("pyfr.version_info(): {}".format(pyfr.get_version_info()))