DOXYGEN := @DOXYGEN@
GRAPHVIZ_DOT := @GRAPHVIZ_DOT@
ANTORA := @ANTORA@
+
+#
+# All supported binding languages.
+#
+comma := ,
+ENABLED_LANGUAGES = @ENABLED_LANGUAGES@
+ENABLED_LANGUAGES_LIST = $(subst $(comma), ,$(ENABLED_LANGUAGES))
build_cpu
build
RADIUSD_VERSION_COMMIT
+ENABLED_LANGUAGES
ANTORA
GRAPHVIZ_DOT
DOXYGEN
ac_user_opts='
enable_option_checking
enable_developer
+enable_language
enable_verify_ptr
enable_largefile
enable_strict_dependencies
--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.
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`
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 #
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
--- /dev/null
+pyfr.egg-info/
+dist/
+build/
--- /dev/null
+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 <jpereira@freeradius.org>
+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
--- /dev/null
+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.
--- /dev/null
+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
+```
+
--- /dev/null
+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
--- /dev/null
+#!/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))
--- /dev/null
+[egg_info]
+tag_build =
+tag_date = 0
--- /dev/null
+#!/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 <jpereira@freeradius.org>
+#
+
+"""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)
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 <freeradius-devel/autoconf.h>
+#include <freeradius-devel/util/conf.h>
+#include <freeradius-devel/util/syserror.h>
+#include <freeradius-devel/util/atexit.h>
+#include <freeradius-devel/util/dict.h>
+#include <freeradius-devel/util/dict_priv.h>
+#include <freeradius-devel/util/version.h>
+
+#define PYFR_TYPE_FLAGS Py_TPFLAGS_HAVE_GC
+#define PYFR_SINGLE_FILE
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <pythread.h>
+#include <structmember.h>
+
+#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
--- /dev/null
+/*
+ * 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 <freeradius-devel/util/pair_legacy.h>
+#include <freeradius-devel/util/proto.h>
+#include <freeradius-devel/radius/radius.h>
+#include <freeradius-devel/protocol/radius/freeradius.internal.h>
+
+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;
+}
--- /dev/null
+/*
+ * 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 <Python.h>
+#include <pythread.h>
+
+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
--- /dev/null
+/*
+ * 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 <freeradius-devel/util/pair_legacy.h>
+#include <freeradius-devel/util/proto.h>
+#include <freeradius-devel/radius/radius.h>
+#include <freeradius-devel/protocol/radius/freeradius.internal.h>
+
+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;
+}
--- /dev/null
+/*
+ * 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 <Python.h>
+#include <pythread.h>
+
+extern PyObject *pyfr_ErrorUtil;
+
+typedef struct {
+ PyObject_HEAD
+} pyfr_util_ctx_t;
+
+PyTypeObject *pyfr_util_register(void);
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+#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 <stdint.h>
+
+#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
--- /dev/null
+#!/bin/bash
+
+for _f in tests/*.py; do
+ echo "CALL $_f"
+ $_f
+done
--- /dev/null
+#!/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
--- /dev/null
+#!/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()))