-# -*- text -*-
-#
-#
-# $Id$
-
-#######################################################################
-#
-# = The default Virtual Server
-#
-# The `default` virtual server is the first one that is enabled on a
-# default installation of FreeRADIUS. This configuration is
-# designed to work in the widest possible set of circumstances, with
-# the widest possible number of authentication methods. This means
-# that in general, you should need to make very few changes to this
-# file.
-#
-# The usual approach is as follows:
-#
-# * configure users in a database (e.g. the `files` module, or in
-# `sql`)
-# * configure the relevant module to talk to the database
-# (e.g. `sql`)
-# * If using EAP / 802.1X, configure the certificates in
-# the `certs/` directory.
-#
-# Then, run the server. This process will ensure that users can log
-# in via PAP, CHAP, MS-CHAP, etc. You should so test the server via
-# `radtest` to verify that it works.
-#
-# ## Editing this file
-#
-# Please read "man radiusd" before editing this file. See the
-# section titled DEBUGGING. It outlines a method where you can
-# quickly obtain the configuration you want, without running into
-# trouble. See also "man unlang", which documents the format of this
-# file. And finally, the debug output can be complex. Please read
-# https://wiki.freeradius.org/radiusd-X to understand that output.
-#
-# The best way to configure the server for your local system is to
-# *carefully* edit this file. Most attempts to make large edits to
-# this file will *break the server*. Any edits should be small, and
-# tested by running the server with `radiusd -X`. Once the edits
-# have been verified to work, save a copy of these configuration
-# files somewhere. We recommend using a revision control system such
-# as `git`, or even a "tar" file. Then, make more edits, and test,
-# as above.
-#
-# There are many "commented out" references to modules and
-# configurations These references serve as place-holders, and as
-# documentation. If you need the functionality of that module, then:
-#
-# * configure the module in `mods-available/`
-# * enable the module in `mods-enabled`. e.g. for LDAP, do: `cd mods-enabled;ln -s ../mods-available/ldap`
-# * uncomment the references to it in this file.
-#
-# In most cases, those small changes will result in the server being
-# able to connect to the database, and to authenticate users.
-
-# ## The Virtual Server
-#
-# This is the `default` virtual server.
-#
server default {
- #
- # namespace::
- #
- # In v4, all "server" sections MUST start with a "namespace"
- # parameter. This tells the server which protocol is being used.
- #
- # All of the "listen" sections in this virtual server will
- # only accept packets for that protocol.
- #
namespace = radius
-
- #
- # ### The listen section
- #
- # The `listen` sections in v4 are very different from the
- # `listen sections in v3. The changes were necessary in
- # order to make FreeRADIUS more flexible, and to make the
- # configuration simpler and more consistent.
- #
listen {
- #
- # type:: The type of packet to accept.
- #
- # Multiple types can be accepted by using multiple
- # lines of `type = ...`.
- #
- # This change from v3 makes it much clearer what kind
- # of packet is being accepted. The old `auth+acct`
- # configuration was awkward and potentially
- # confusing.
- #
type = Access-Request
- type = Status-Server
-
- #
- # transport:: The transport protocol.
- #
- # The allowed transports for RADIUS are currently
- # `udp` and `tcp`. A `listen` section can only have
- # one `transport` defined. For multiple transports,
- # use multiple `listen` sections.
- #
- # You can have a "headless" server by commenting out
- # the "transport" configuration. A "headless" server
- # will process packets from other virtual servers,
- # but will not accept packets from the network.
- #
- # The `inner-tunnel` server is an example of a
- # headless server. It accepts packets from the
- # "inner tunnel" portion of PEAP and TTLS. But it
- # does not accept those packets from the network.
- #
transport = udp
-
- #
- # limit:: limits for this socket.
- #
- # The `limit` section contains configuration items
- # which enforce various limits on the socket. These
- # limits are usually transport-specific.
- #
- # Limits are used to prevent "run-away" problems.
- #
- limit {
- #
- # max_clients:: The maximum number of dynamic
- # clients which can be defined for this
- # listener.
- #
- # If dynamic clients are not used, then this
- # configuration item is ignored.
- #
- # The special value of `0` means "no limit".
- # We do not recommend using `0`, as attackers
- # could forge packets from the entire
- # Internet, and cause FreeRADIUS to run out
- # of memory.
- #
- # This configuration item should be set to
- # the number of individual RADIUS clients
- # (e.g. NAS, AP, etc.) which will be sending
- # packets to FreeRADIUS.
- #
- max_clients = 256
-
- #
- # max_connections:: The maximum number of
- # connected sockets which will be accepted
- # for this listener.
- #
- # Each connection opens a new socket, so be
- # aware of system file descriptor
- # limitations.
- #
- # If the listeners do not use connected
- # sockets (e.g. TCP), then this configuration
- # item is ignored.
- #
- max_connections = 256
-
- #
- # idle_timeout:: Time after which idle
- # connections or dynamic clients are deleted.
- #
- # Useful range of values: 5 to 600
- #
- idle_timeout = 60.0
-
- #
- # nak_lifetime:: Time for which blocked
- # clients are placed into a NAK cache.
- #
- # If a dynamic client is disallowed, it is
- # placed onto a "NAK" list for a period
- # of time. This process helps to prevent
- # DoS attacks. When subsequent packets are
- # received from that IP address, they hit the
- # "NAK" cache, and are immediately discarded.
- #
- # After `nak_timeout` seconds, the blocked
- # entry will be removed, and the IP will be
- # allowed to try again to define a dynamic
- # client.
- #
- # Useful range of values: 1 to 600
- #
- nak_lifetime = 30.0
-
- #
- # cleanup_delay:: The time to wait (in
- # seconds) before cleaning up a reply to an
- # `Access-Request` packet.
- #
- # The reply is normally cached internally for
- # a short period of time, after it is sent to
- # the NAS. The reply packet may be lost in
- # the network, and the NAS will not see it.
- # The NAS will then resend the request, and
- # the server will respond quickly with the
- # cached reply.
- #
- # If this value is set too low, then
- # duplicate requests from the NAS MAY NOT be
- # detected, and will instead be handled as
- # separate requests.
- #
- # If this value is set too high, then the
- # server will use more memory for no benefit.
- #
- # This value can include a decimal number of
- # seconds, e.g. "4.1".
- #
- # Useful range of values: 2 to 30
- #
- cleanup_delay = 5.0
- }
-
- #
- # #### UDP Transport
- #
- # When the `listen` section contains `transport =
- # udp`, it looks for a "udp" subsection. This
- # subsection contains all of the configuration for
- # the UDP transport.
- #
udp {
- #
- # ipaddr:: The IP address where FreeRADIUS
- # accepts packets.
- #
- # The address can be IPv4, IPv6, a numbered
- # IP address, or a host name. If a host name
- # is used, the IPv4 address is preferred.
- # When there is no IPv4 address for a host
- # name, the IPv6 address is used.
- #
- # As with UDP, `ipaddr`, `ipv4addr`, and `ipv6addr`
- # are all allowed.
- #
- # ipv4addr:: Use IPv4 addresses.
- #
- # The same as `ipaddr`, but will only use
- # IPv4 addresses.
- #
- # ipv6addr:: Use IPv6 addresses.
- #
- # The same as `ipaddr`, but will only use
- # IPv6 addresses.
- #
ipaddr = *
-
- #
- # port:: the UDP where FreeRADIUS accepts
- # packets.
- #
- # The default port for Access-Accept packets
- # is `1812`.
- #
port = 1812
-
- #
- # dynamic_clients:: Whether or not we allow
- # dynamic clients.
- #
- # If set to `true`, then packets from unknown
- # clients are passed through the `new
- # client` subsection below. See that section
- # for more information about how dynamic
- # clients work.
- #
-# dynamic_clients = true
-
- #
- # networks:: The list of networks which are
- # allowed to send packets to FreeRADIUS for
- # dynamic clients.
- #
- # If there are no dynamic clients, then this
- # section is ignored.
- #
- # The purpose of the `networks` subsection is
- # to ensure that only a small set of source
- # IPs can trigger dynamic clients. If anyone
- # could trigger dynamic clients, then the
- # server would be subject to a DoS attack.
- #
- networks {
- #
- # allow:: Allow packets from these
- # networks to define dynamic clients.
- #
- # Packets from all other sources will
- # be rejected.
- #
- # When a packet is from an allowed
- # network, it will be run through the
- # `new client` subsection below.
- # That subsection can still reject
- # the client request.
- #
- # There is no limit to the number of
- # networks which can be listed here.
- #
- allow = 127/8
- allow = 192.0.2/24
-
- #
- # deny:: deny some networks.
- #
- # The default behavior is to only
- # allow packets from the `allow`
- # networks. The `deny` directive
- # allows you to carve out a subset of
- # an `allow` network, where some
- # packets are denied.
- #
- # That is, a `deny` network MUST
- # exist within a previous `allow` network.
- #
- # The `allow` and `deny` rules apply
- # only to networks. The order which
- # they appear in the configuration
- # file does not matter.
- #
-# deny = 127.0.0/24
- }
}
-
- #
- # #### TCP Transport
- #
- # When the configuration has `transport = tcp`, it
- # looks for a `tcp` subsection. That subsection
- # contains all of the configuration for the TCP
- # transport.
- #
- # Since UDP and TCP are similar, the majority of the
- # configuration items are the same for both of them.
- #
- tcp {
- #
- # ipaddr:: The IP address where FreeRADIUS
- # accepts packets.
- #
- # It has the same definition and meaning as
- # the UDP `ipaddr` configuration above.
- #
- ipaddr = *
-
- #
- # NOTE: As with v3, `ipaddr`, `ipv4addr`, and `ipv6addr`
- # are all allowed.
- #
-
- #
- # port:: the TCP where FreeRADIUS accepts
- # packets.
- #
- # The default port for Access-Accept packets
- # is `1812`.
- #
- port = 1812
-
- #
- # dynamic_clients:: Whether or not we allow dynamic clients.
- #
- # If set to true, then packets from unknown
- # clients are passed through the "new client"
- # subsection below. See that section for
- # more information.
- #
-# dynamic_clients = true
-
- #
- # networks { ... }::
- #
- # If dynamic clients are allowed, then limit
- # them to only a small set of source
- # networks.
- #
- # If dynamic clients are not allowed, then
- # this section is ignored.
- #
- networks {
- #
- # allow:: Allow packets from a network.
- #
- # deny:: Deny packets from a network.
- #
- # Allow or deny packets from these networks
- # to define dynamic clients.
- #
- # Packets from all other sources will
- # be discarded.
- #
- # Even if a packet is from an allowed
- # network, it still must be permitted
- # by the "new client" subsection.
- #
- # There is no limit to the number of
- # networks which can be listed here.
- #
- # The allow / deny checks are organised by
- # address. The order of the items given here
- # does not matter.
- #
- allow = 127/8
- allow = 192.0.2/24
-# deny = 127.0.0/24
- }
- }
-
- #
- # #### Access-Request subsection
- #
- # This section contains configuration which is
- # specific to processing `Access-Request` packets.
- #
- # Similar sections can be added, but are not
- # necessary for Accounting-Request (and other)
- # packets. At this time, there is no configuration
- # needed for other packet types.
- #
- Access-Request {
- #
- # log:: Logging configuration for `Access-Request` packets
- #
- # In v3, the `Access-Request` logging was
- # configured in the main `radiusd.conf` file,
- # in the main `log` subsection. That
- # limitation meant that the configuration was
- # global to FreeRADIUS. i.e. you could not
- # have different `Access-Request` logging for
- # different virtual server.
- #
- # The extra configuration in v4 allows for
- # increased flexibility.
- #
- log {
- #
- # stripped_names:: Log the full
- # `User-Name` attribute, as it was
- # found in the request.
- #
- # allowed values: {no, yes}
- #
- stripped_names = no
-
- #
- # auth:: Log authentication requests
- # to the log file.
- #
- # allowed values: {no, yes}
- #
- auth = no
-
- #
- # auth_goodpass:: Log "good"
- # passwords with the authentication
- # requests.
- #
- # allowed values: {no, yes}
- #
- auth_goodpass = no
-
- #
- # auth_badpass:: Log "bad"
- # passwords with the authentication
- # requests.
- #
- # allowed values: {no, yes}
- #
- auth_badpass = no
-
- #
- # msg_goodpass::
- # msg_badpass::
- #
- # Log additional text at the end of the "Login OK" messages.
- # for these to work, the "auth" and "auth_goodpass" or "auth_badpass"
- # configurations above have to be set to "yes".
- #
- # The strings below are dynamically expanded, which means that
- # you can put anything you want in them. However, note that
- # this expansion can be slow, and can negatively impact server
- # performance.
- #
-# msg_goodpass = ""
-# msg_badpass = ""
-
- #
- # msg_denied::
- #
- # The message when the user exceeds the Simultaneous-Use limit.
- #
- msg_denied = "You are already logged in - access denied"
- }
-
- #
- # session:: Controls how ongoing
- # (multi-round) sessions are handled
- #
- # This section is primarily useful for EAP.
- # It controls the number of EAP
- # authentication attempts that can occur
- # concurrently.
- #
- session {
- #
- # max:: The maximum number of ongoing sessions
- #
-# max = 4096
-
- #
- # timeout:: How long to wait before expiring a
- # session.
- #
- # The timer starts when a response
- # with a state value is sent. The
- # timer stops when a request
- # containing the previously sent
- # state value is received.
- #
-# timeout = 15
- }
- }
- }
-
- listen {
- type = Access-Request
- type = Status-Server
-
- transport = tcp
-
- tcp {
- #
- # As with v3, "ipaddr", "ipv4addr", and "ipv6addr"
- # are all allowed.
- #
- ipaddr = *
- port = 1812
-
- #
- # Whether or not we allow dynamic clients.
- #
- # If set to true, then packets from unknown
- # clients are passed through the "new client"
- # subsection below. See that section for
- # more information.
- #
-# dynamic_clients = true
-
- #
- # If dynamic clients are allowed, then limit
- # them to only a small set of source
- # networks.
- #
- # If dynamic clients are not allowed, then
- # this section is ignored.
- #
- networks {
- #
- # Allow packets from these networks
- # to define dynamic clients.
- #
- # Packets from all other sources will
- # be rejected.
- #
- # Even if a packet is from an allowed
- # network, it still must be allowed
- # by the "new client" subsection.
- #
- # There is no limit to the number of
- # networks which can be listed here.
- #
- allow = 127/8
- allow = 192.0.2/24
-# deny = 127.0.0/24
- }
- }
-
- #
- # #### Access-Request subsection
- #
- # This section contains configuration which is
- # specific to processing `Access-Request` packets.
- #
- # Similar sections can be added, but are not
- # necessary for Accounting-Request (and other)
- # packets. At this time, there is no configuration
- # needed for other packet types.
- #
- Access-Request {
- #
- # log:: Logging configuration for `Access-Request` packets
- #
- # In v3, the `Access-Request` logging was
- # configured in the main `radiusd.conf` file,
- # in the main `log` subsection. That
- # limitation meant that the configuration was
- # global to FreeRADIUS. i.e. you could not
- # have different `Access-Request` logging for
- # different virtual server.
- #
- # The extra configuration in v4 allows for
- # increased flexibility.
- #
- log {
- # stripped_names:: Log the full
- # `User-Name` attribute, as it was
- # found in the request.
- #
- # allowed values: {no, yes}
- #
- stripped_names = no
-
- # auth:: Log authentication requests
- # to the log file.
- #
- # allowed values: {no, yes}
- #
- auth = no
-
- # auth_goodpass:: Log "good"
- # passwords with the authentication
- # requests.
- #
- # allowed values: {no, yes}
- #
- auth_badpass = no
-
- # auth_badpass:: Log "bad"
- # passwords with the authentication
- # requests.
- #
- # allowed values: {no, yes}
- #
- auth_goodpass = no
-
- # Log additional text at the end of the "Login OK" messages.
- # for these to work, the "auth" and "auth_goodpass" or "auth_badpass"
- # configurations above have to be set to "yes".
- #
- # The strings below are dynamically expanded, which means that
- # you can put anything you want in them. However, note that
- # this expansion can be slow, and can negatively impact server
- # performance.
- #
-# msg_goodpass = ""
-# msg_badpass = ""
-
- # The message when the user exceeds the Simultaneous-Use limit.
- #
- msg_denied = "You are already logged in - access denied"
- }
-
- #
- # session:: Controls how ongoing
- # (multi-round) sessions are handled
- #
- # This section is primarily useful for EAP.
- # It controls the number of EAP
- # authentication attempts that can occur
- # concurrently.
- #
- session {
- #
- # max:: The maximum number of ongoing sessions
- #
-# max = 4096
-
- # timeout:: How long to wait before expiring a
- # session.
- #
- # The timer starts when a response
- # with a state value is sent. The
- # timer stops when a request
- # containing the previously sent
- # state value is received.
- #
-# timeout = 15
- }
- }
- }
-
- #
- # ### Listen for Accounting-Request packets
- #
- listen {
- type = Accounting-Request
-
- transport = udp
-
- udp {
- ipaddr = *
- port = 1813
- }
- }
-
-
- #
- # ### Local Clients
- #
- # The "client" sections can can also be placed here. Unlike
- # v3, they do not need to be wrapped in a "clients" section.
- # They can just co-exist beside the "listen" sections.
- #
- # Clients listed here will apply to *all* listeners in this
- # virtual server.
- #
- # The clients listed here take precedence over the global
- # clients.
- #
- client localhost {
- shortname = sample
- ipaddr = 192.0.2.1
- secret = testing123
-
- # The other "client" configuration items can be added
- # here, too.
}
-
-######################################################################
-#
-# ## Packet Processing sections
-#
-# The sections below are called when a RADIUS packet has been
-# received.
-#
-# * recv Access-Request - for authorization and authentication
-# * recv Status-Server - for checking the server is responding
-#
-######################################################################
-
-#
-# ### Receive Access-Request packets
-#
-recv Access-Request {
- #
- # Take a `User-Name`, and perform some checks on it, for
- # spaces and other invalid characters. If the `User-Name`
- # is invalid, reject the request.
- #
- # See policy.d/filter for the definition of the
- # filter_username policy.
- #
- filter_username
-
- #
- # Some broken equipment sends passwords with embedded
- # zeros, i.e. the debug output will show:
- #
- # User-Password = "password\000\000"
- #
- # This policy will fix the password to just be "password".
- #
-# filter_password
-
- #
- # If you intend to use CUI and you require that the
- # Operator-Name be set for CUI generation and you want to
- # generate CUI also for your local clients, then uncomment
- # operator-name below and set the operator-name for
- # your clients in clients.conf.
- #
-# operator-name
-
- #
- # Proxying example
- #
- # The following example will proxy the request if the
- # username ends in example.com.
- #
-# if (&User-Name =~ /@example\.com$/) {
-# update control {
-# &Auth-Type := "proxy-example.com"
-# }
-# }
-
- #
- # If you want to generate CUI for some clients that do
- # not send proper CUI requests, then uncomment cui below
- # and set "add_cui = yes" for these clients in
- # clients.conf.
- #
-# cui
-
- #
- # The `auth_log` module will write all `Access-Request` packets to a file.
- #
- # Uncomment the next bit in order to have a log of
- # authentication requests. For more information, see
- # `mods-available/detail.log`.
- #
-# auth_log
-
- #
- # The `chap` module will set `Auth-Type := CHAP` if the
- # packet contains a `CHAP-Challenge` attribute. The module
- # does this only if the `Auth-Type` attribute has not already
- # been set.
- #
- chap
-
- #
- # The `mschap` module will set `Auth-Type := mschap` if the
- # packet contains an `MS-CHAP-Challenge` attribute. The
- # module does this only if the `Auth-Type` attribute has not
- # already been set.
- #
- mschap
-
- #
- # The `digest` module implements the SIP Digest
- # authentication method.
- #
- # Note that the module does not implement RFC 4590. Instead,
- # it implements an earlier draft of the specification. Since
- # all of the NAS equipment also implements the earlier draft,
- # this limitation is fine.
- #
- # If you have a Cisco SIP server authenticating against
- # FreeRADIUS, the `digest` module will set `Auth-Type :=
- # "Digest"` if we are handling a SIP Digest request and the
- # `Auth-Type` has not already been set.
- #
- digest
-
- #
- # The `wimax` module fixes up various WiMAX-specific stupidities.
- #
- # The WiMAX specification says that the `Calling-Station-Id`
- # is 6 octets of the MAC. This definition conflicts with RFC
- # 3580, and all common RADIUS practices. Uncommenting the
- # `wimax` module here allows the module to change the
- # `Calling-Station-Id` attribute to the normal format as
- # specified in RFC 3580 Section 3.21.
- #
-# wimax
-
- #
- # The `eap` module takes care of all EAP authentication,
- # including EAP-MD5, EAP-TLS, PEAP and EAP-TTLS.
- #
- # The module also sets the EAP-Type attribute in the request
- # list, to the incoming EAP type.
- #
- # The `eap` module returns `ok` if it is not yet ready to
- # authenticate the user. The configuration below checks for
- # that return value, and if so, stops processing the current
- # section.
- #
- # The result is that any LDAP and/or SQL servers will not be
- # queried during the initial set of packets that go back and
- # forth to set up EAP-TTLS or PEAP.
- #
- # We also recommend doing user lookups in the `inner-tunnel`
- # virtual server.
- #
- eap {
- ok = return
+ recv Access-Request {
+ python
}
+<<<<<<< Updated upstream
#
# The `unix` module will obtain passwords from `/etc/passwd`
#
attr_filter.accounting_response
}
+=======
+>>>>>>> Stashed changes
}
* @file rlm_python.c
* @brief Translates requests between the server an a python interpreter.
*
- * @note Rewritten by Paul P. Komkoff Jr <i@stingr.net>.
+ * @note Rewritten by Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * very little of the original code remains.
*
- * @copyright 2000,2006,2015-2016 The FreeRADIUS server project
+ * @copyright 2020-2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @copyright 2000,2006,2015-2021 The FreeRADIUS server project
* @copyright 2002 Miguel A.L. Paraz (mparaz@mparaz.com)
* @copyright 2002 Imperium Technology, Inc.
*/
#include <freeradius-devel/util/lsan.h>
#include <Python.h>
+#include <structmember.h>
+
#include <frameobject.h> /* Python header not pulled in by default. */
#include <libgen.h>
#include <dlfcn.h>
+#include <pthread.h>
/** Specifies the module.function to load for processing a section
*
* thread must have a PyThreadState per interpreter, to track execution.
*/
typedef struct {
- PyThreadState *state; //!< Module instance/thread specific state.
+ PyThreadState *state; //!< Module instance/thread specific state.
} rlm_python_thread_t;
+/** Additional fields for pairs
+ *
+ */
+typedef struct {
+ PyObject_HEAD //!< Common fields needed for every python object.
+ fr_pair_list_t *head; //!< of the pair list.
+ fr_cursor_t iter; //!< Holds state for the iterator.
+ bool iter_init; //!< Whether the iterator has been initialised.
+ tmpl_t *tmpl; //!< Describes which attribute is being accessed.
+} py_freeradius_pair_t;
+
+typedef struct {
+ py_freeradius_pair_t pair; //!< Fields from the pair struct.
+ tmpl_pair_list_t list_ref; //!< List this structure represents.
+} py_freeradius_pair_list_t;
+
+typedef struct {
+ PyObject_HEAD //!< Common fields needed for every python object.
+ PyObject *request; //!< Request list.
+ PyObject *reply; //!< Reply list.
+ PyObject *control; //!< Control list.
+ PyObject *state; //!< Session state list.
+} py_freeradius_pair_root_t;
+
+typedef struct {
+ PyObject_HEAD //!< Common fields needed for every python object.
+ request_t *request; //!< The current request.
+ PyObject *pairs; //!< Pair root.
+} py_freeradius_request_t;
+
+/** Wrapper around a python instance
+ *
+ * This is added to the FreeRADIUS module to allow us to
+ * get at the global and thread local instance data.
+ */
+typedef struct {
+ PyObject_HEAD //!< Common fields needed for every python object.
+ rlm_python_t *inst; //!< Global python instance.
+ rlm_python_thread_t *t; //!< Thread-specific python instance.
+ request_t *request; //!< Current request.
+} py_freeradius_state_t;
+
static void *python_dlhandle;
static PyThreadState *global_interpreter; //!< Our first interpreter.
static rlm_python_t *current_inst; //!< Used for communication with inittab functions.
+static rlm_python_thread_t *current_t; //!< Used for communication with inittab functions.
+
static CONF_SECTION *current_conf; //!< Used for communication with inittab functions.
static char *default_path; //!< The default python path.
+static PyObject *py_freeradius_log(UNUSED PyObject *self, PyObject *args, PyObject *kwds);
+static int py_freeradius_state_init(PyObject *self, UNUSED PyObject *args, UNUSED PyObject *kwds);
+static int py_freeradius_pair_list_init(PyObject *self, PyObject *args, PyObject *kwds);
+
+static PyObject *py_freeradius_pair_map_subscript(PyObject *self, PyObject *attr);
+static int py_freeradius_pair_init(PyObject *self, PyObject *args, PyObject *kwds);
+
+static void python_error_log(rlm_python_t const *inst, request_t *request);
+
+/** The class which all pair types inherit from
+ *
+ */
+static PyTypeObject py_freeradius_pair_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.Pair",
+ .tp_doc = "An attribute value pair",
+ .tp_basicsize = sizeof(py_freeradius_pair_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_new = PyType_GenericNew,
+ .tp_init = py_freeradius_pair_init,
+
+};
+
+/** Contains a list of one or more value pairs of a specific type
+ *
+ */
+static PyTypeObject py_freeradius_value_pair_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.ValuePairList",
+ .tp_doc = "A value pair, i.e. one of the type string, integer, ipaddr etc...)",
+ .tp_basicsize = sizeof(py_freeradius_pair_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_base = &py_freeradius_pair_def,
+ .tp_new = PyType_GenericNew,
+ .tp_init = py_freeradius_pair_init,
+ .tp_as_mapping = &(PyMappingMethods){
+ .mp_subscript = py_freeradius_pair_map_subscript
+ }
+};
+
+/** Contains group attribute of a specific type
+ *
+ * Children of this attribute may be accessed using the map protocol
+ * i.e. foo['child-of-foo'].
+ *
+ */
+static PyTypeObject py_freeradius_grouping_pair_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.GroupingPair",
+ .tp_doc = "A grouping pair, i.e. one of the type group, tlv, vsa or vendor. "
+ "Children are accessible via the mapping protocol i.e. foo['child-of-foo]"
+ .tp_basicsize = sizeof(py_freeradius_pair_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_base = &py_freeradius_pair_def,
+ .tp_new = PyType_GenericNew,
+ .tp_init = py_freeradius_pair_init,
+ .tp_as_mapping = &(PyMappingMethods){
+ .mp_subscript = py_freeradius_pair_map_subscript
+ }
+};
+
+/** Contains a list of one or more grouping pairs of a specific type
+ *
+ * As a convenience children may be accessed directly using the map
+ * interface, i.e. foo['child-of-foo'], which is equivalent to
+ * foo[0]['child-of-foo'] similar to attribute reference syntax in unlang.
+ * This is a bit of a hack, but useful, and comes naturally from the fact
+ * that GroupingPairList subclasses GroupingPair.
+ *
+ * Accessing indexes i.e. foo[n] will return the n'th instance of the
+ * grouping attribute.
+ *
+ */
+static PyTypeObject py_freeradius_grouping_pair_list_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.GroupingPairList",
+ .tp_doc = "An ordered list of freeradius.GroupingPair objects",
+ .tp_basicsize = sizeof(py_freeradius_pair_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_base = &py_freeradius_grouping_pair_def,
+ .tp_new = PyType_GenericNew,
+ .tp_init = py_freeradius_pair_init,
+};
+
+/** Each instance contains a top level list (i.e. request, reply, control, session-state)
+ */
+static PyTypeObject py_freeradius_leagcy_pair_list_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.LegacyPairList",
+ .tp_doc = "A list of objects of freeradius.GroupingPairList and freeradius.ValuePair",
+ .tp_basicsize = sizeof(py_freeradius_pair_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_base = &py_freeradius_pair_def,
+ .tp_new = PyType_GenericNew,
+ .tp_init = py_freeradius_pair_list_init, /* Does nothing, just stops parent init being called */
+ .tp_as_mapping = &(PyMappingMethods){
+ .mp_subscript = py_freeradius_pair_map_subscript
+ }
+};
+
+static PyMemberDef py_freeradius_pair_root_attrs[] = {
+ {
+ .name = "request",
+ .type = T_OBJECT,
+ .offset = offsetof(py_freeradius_pair_root_t, request),
+ .flags = READONLY,
+ .doc = "Pairs in the request list - received from the network"
+ },
+ {
+ .name = "reply",
+ .type = T_OBJECT,
+ .offset = offsetof(py_freeradius_pair_root_t, reply),
+ .flags = READONLY,
+ .doc = "Pairs in the reply list - sent to the network"
+ },
+ {
+ .name = "control",
+ .type = T_OBJECT,
+ .offset = offsetof(py_freeradius_pair_root_t, control),
+ .flags = READONLY,
+ .doc = "Pairs in the control list - control the behaviour of subsequently called modules"
+ },
+ {
+ .name = "session-state",
+ .type = T_OBJECT,
+ .offset = offsetof(py_freeradius_pair_root_t, state),
+ .flags = READONLY,
+ .doc = "Pairs in the session-state list - persists for the length of the session"
+ },
+ { NULL } /* Terminator */
+};
+
+static PyTypeObject py_freeradius_pair_root_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.PairRoot",
+ .tp_doc = "Root of all pair lists associated with the request",
+ .tp_basicsize = sizeof(py_freeradius_pair_root_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_members = py_freeradius_pair_root_attrs,
+ .tp_getattro = PyObject_GenericGetAttr
+};
+
+static PyMemberDef py_freeradius_request_attrs[] = {
+ {
+ .name = "pairs",
+ .type = T_OBJECT,
+ .offset = offsetof(py_freeradius_request_t, pairs),
+ .flags = READONLY,
+ .doc = "Object providing access to all pair lists associated with the request "
+ "(.request, .reply, .control, .session-state)"
+ },
+ { NULL } /* Terminator */
+};
+
+static PyTypeObject py_freeradius_request_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.Request",
+ .tp_doc = "freeradius request handle",
+ .tp_basicsize = sizeof(py_freeradius_request_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_members = py_freeradius_request_attrs
+};
+
+static PyTypeObject py_freeradius_state_def = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "freeradius.State",
+ .tp_doc = "Private state data",
+ .tp_basicsize = sizeof(py_freeradius_state_t),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = PyType_GenericNew,
+ .tp_init = py_freeradius_state_init
+};
+
/*
- * As of Python 3.8 the GIL will be per-interpreter
- * If there are still issues with CEXTs,
- * multiple interpreters and the GIL at that point
- * users can build rlm_python against Python 3.8
- * and the horrible hack of using a single interpreter
- * for all instances of rlm_python will no longer be
- * required.
+ * radiusd Python functions
+ */
+static PyMethodDef py_freeradius_methods[] = {
+ { "log", (PyCFunction)&py_freeradius_log, METH_VARARGS | METH_KEYWORDS,
+ "freeradius.log(msg[, type, lvl])\n\n"
+ "Print a message using the freeradius daemon's logging system.\n"
+ "type should be one of the following constants:\n"
+ " freeradius.L_DBG\n"
+ " freeradius.L_INFO\n"
+ " freeradius.L_WARN\n"
+ " freeradius.L_ERR\n"
+ "lvl should be one of the following constants:\n"
+ " freeradius.L_DBG_LVL_OFF\n"
+ " freeradius.L_DBG_LVL_1\n"
+ " freeradius.L_DBG_LVL_2\n"
+ " freeradius.L_DBG_LVL_3\n"
+ " freeradius.L_DBG_LVL_4\n"
+ " freeradius.L_DBG_LVL_MAX\n"
+ },
+ { NULL, NULL, 0, NULL },
+};
+
+static PyModuleDef py_freeradius_def = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "freeradius",
+ .m_doc = "Freeradius python module",
+ .m_size = -1,
+ .m_methods = py_freeradius_methods
+};
+
+/** Return the module instance object associated with the thread state or interpreter state
+ *
+ */
+static inline CC_HINT(always_inline) py_freeradius_state_t *rlm_python_state_obj(void)
+{
+ PyObject *dict;
+
+ dict = PyThreadState_GetDict(); /* If this is NULL, we're dealing with the main interpreter */
+ if (!dict) {
+ PyObject *module;
+
+ module = PyState_FindModule(&py_freeradius_def);
+ if (unlikely(!module)) return NULL;
+
+ dict = PyModule_GetDict(module);
+ if (unlikely(!dict)) return NULL;
+ }
+
+ return (py_freeradius_state_t *)PyDict_GetItemString(dict, "__State");
+}
+
+/** Return the rlm_python instance associated with the current interpreter
+ *
+ */
+static rlm_python_t const *rlm_python_get_inst(void)
+{
+ py_freeradius_state_t const *p_state;
+
+ p_state = rlm_python_state_obj();
+ if (unlikely(!p_state)) return NULL;
+
+ return p_state->inst;
+}
+
+#if 0
+/** Return the rlm_python thread instance associated with the current interpreter
+ *
+ */
+static rlm_python_thread_t const *rlm_python_get_thread_inst(void)
+{
+ py_freeradius_state_t const *p_state;
+
+ p_state = rlm_python_state_obj();
+ if (unlikely(!p_state)) return NULL;
+
+ return p_state->t;
+}
+#endif
+
+/** Return the request associated with the current thread state
+ *
+ */
+static request_t *rlm_python_get_request(void)
+{
+ py_freeradius_state_t const *p_state;
+
+ p_state = rlm_python_state_obj();
+ if (unlikely(!p_state)) return NULL;
+
+ return p_state->request;
+}
+
+/** Set the request associated with the current thread state
+ *
+ */
+static void rlm_python_set_request(request_t *request)
+{
+ py_freeradius_state_t *p_state;
+
+ p_state = rlm_python_state_obj();
+ if (unlikely(!p_state)) return;
+
+ p_state->request = request;
+}
+
+/** Allow fr_log to be called from python
*
- * As Python 3.x module initialisation is significantly
- * different than Python 2.x initialisation,
- * it'd be a pain to retain the cext_compat for
- * Python 3 and as Python 3 users have the option of
- * using as version of Python which fixes the underlying
- * issue, we only support using a global interpreter
- * for Python 2.7 and below.
*/
+static PyObject *py_freeradius_log(UNUSED PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char const *kwlist[] = { "msg", "type", "lvl", NULL };
+ char *msg;
+ int type = L_DBG;
+ int lvl = L_DBG_LVL_2;
+ rlm_python_t const *inst = rlm_python_get_inst();
+
+ if (fr_debug_lvl < lvl) Py_RETURN_NONE; /* Don't bother parsing args */
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|ii", (char **)((uintptr_t)kwlist),
+ &msg, &type, &lvl)) Py_RETURN_NONE;
+
+ fr_log(&default_log, type, __FILE__, __LINE__, "rlm_python (%s) - %s", inst->name, msg);
+
+ Py_RETURN_NONE;
+}
+
+static int py_freeradius_state_init(PyObject *self, UNUSED PyObject *args, UNUSED PyObject *kwds)
+{
+ py_freeradius_state_t *our_self = (py_freeradius_state_t *)self;
+ rlm_python_t *inst = current_inst; /* Needed for debug messages */
+
+ fr_assert(current_inst);
+
+ our_self->inst = talloc_get_type_abort(current_inst, rlm_python_t);
+ our_self->t = current_t ? talloc_get_type_abort(current_t, rlm_python_thread_t) : NULL; /* May be NULL if this is the first interpreter */
+
+ DEBUG3("Populating __State data with %p/%p", our_self->inst, our_self->t);
+
+ return 0;
+}
+
+/** Returns a freeradius.Pair, either from the parent's cache or by pulling a representation over from C land
+ *
+ */
+static PyObject *py_freeradius_pair_map_subscript(PyObject *self, PyObject *attr)
+{
+ PyObject *args;
+ PyObject *py_pair;
+ rlm_python_t const *inst;
+
+ if (DEBUG_ENABLED3) inst = rlm_python_get_inst();
+
+ DEBUG3("Dynamically instantiating pair");
+ args = PyTuple_New(2);
+ if (unlikely(!args)) {
+ error:
+ /* TODO - Raise exception */
+ Py_XDECREF(args);
+ Py_RETURN_NONE;
+ }
+ if (PyTuple_SetItem(args, 0, self) != 0) goto error;
+ if (PyTuple_SetItem(args, 1, attr) != 0) goto error;
+
+ py_pair = PyObject_CallObject((PyObject *)&py_freeradius_pair_def, args);
+ if (!py_pair) goto error;
+
+ Py_DECREF(args);
+
+ return py_pair;
+}
+
+static int py_freeradius_pair_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char const *kwlist[] = { "parent", "ref", NULL };
+
+ request_t *request = rlm_python_get_request();
+ py_freeradius_pair_t *our_self = (py_freeradius_pair_t *)self;
+
+ PyObject *py_parent;
+
+ Py_buffer ref;
+
+ tmpl_rules_t t_rules = {
+ .disallow_qualifiers = true,
+ .disallow_filters = true, /* This all has to be handled within python */
+ .at_runtime = true,
+ .prefix = TMPL_ATTR_REF_PREFIX_NO /* No & allowed in fields */
+ };
+ int inst = -1;
+
+ if (!request) {
+ /* TODO - Throw exception */
+ return -1;
+ }
+
+ /*
+ * Parse parent of type 'Pair' and ref which is
+ * a string.
+ */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!s*|i", (char **)((uintptr_t)kwlist),
+ (PyObject *)&py_freeradius_pair_def, &py_parent, &ref, &inst)) return -1;
+ t_rules.dict_def = request->dict;
+
+ /*
+ * If the parent is a list, we use is as the
+ * default. Caller shouldn't be providing
+ * qualifiers anyway.
+ */
+ if (PyObject_IsInstance(py_parent, (PyObject *)&py_freeradius_leagcy_pair_list_def)) {
+ t_rules.list_def = ((py_freeradius_pair_list_t *)py_parent)->list_ref;
+ /*
+ * If the parent isn't a list, then we use it as
+ * the nested parent.
+ */
+ } else {
+ t_rules.attr_parent = tmpl_da(((py_freeradius_pair_t *)py_parent)->tmpl);
+ }
+
+ tmpl_afrom_attr_substr(NULL, NULL, &our_self->tmpl,
+ &FR_SBUFF_IN((char const *)ref.buf, ref.len), NULL, &t_rules);
+ if (!our_self->tmpl) {
+ /* TODO - Throw exception */
+ RPERROR("ref=%.*s is invalid", (int)ref.len, ref.buf);
+ return -1;
+ }
+
+ if (inst > 0) {
+ if (inst > UINT16_MAX) {
+ /* TODO - Throw exception */
+ return -1;
+ }
+
+ tmpl_attr_set_leaf_num(our_self->tmpl, (uint16_t)inst);
+ }
+
+ return 0;
+}
+
+static int py_freeradius_pair_list_init(UNUSED PyObject *self, UNUSED PyObject *args, UNUSED PyObject *kwds)
+{
+ return 0;
+}
/*
* A mapping of configuration file names to internal variables.
#define A(x) { #x, x },
A(L_DBG)
- A(L_WARN)
A(L_INFO)
- A(L_ERR)
A(L_WARN)
- A(L_DBG_WARN)
- A(L_DBG_ERR)
- A(L_DBG_WARN_REQ)
- A(L_DBG_ERR_REQ)
+ A(L_ERR)
+ A(L_DBG_LVL_OFF)
+ A(L_DBG_LVL_1)
+ A(L_DBG_LVL_2)
+ A(L_DBG_LVL_3)
+ A(L_DBG_LVL_4)
+ A(L_DBG_LVL_MAX)
A(RLM_MODULE_REJECT)
A(RLM_MODULE_FAIL)
A(RLM_MODULE_OK)
{ NULL, 0 },
};
-/*
- * radiusd Python functions
- */
-
-/** Allow fr_log to be called from python
- *
- */
-static PyObject *mod_log(UNUSED PyObject *module, PyObject *args)
-{
- int status;
- char *msg;
-
- if (!PyArg_ParseTuple(args, "is", &status, &msg)) {
- Py_RETURN_NONE;
- }
-
- fr_log(&default_log, status, __FILE__, __LINE__, "%s", msg);
-
- Py_RETURN_NONE;
-}
-
-static PyMethodDef module_methods[] = {
- { "log", &mod_log, METH_VARARGS,
- "freeradius.log(level, msg)\n\n" \
- "Print a message using the freeradius daemon's logging system. level should be one of the\n" \
- "following constants L_DBG, L_WARN, L_INFO, L_ERR, L_DBG_WARN, L_DBG_ERR, L_DBG_WARN_REQ, L_DBG_ERR_REQ\n"
- },
- { NULL, NULL, 0, NULL },
-};
-
/** Print out the current error
*
* Must be called with a valid thread state set
*/
-static void python_error_log(const rlm_python_t *inst, request_t *request)
+static void python_error_log(rlm_python_t const *inst, request_t *request)
{
PyObject *p_type = NULL, *p_value = NULL, *p_traceback = NULL, *p_str_1 = NULL, *p_str_2 = NULL;
PyFrameObject *cur_frame = ptb->tb_frame;
ROPTIONAL(RERROR, ERROR, "[%ld] %s:%d at %s()",
- fnum,
- PyUnicode_AsUTF8(cur_frame->f_code->co_filename),
- PyFrame_GetLineNumber(cur_frame),
- PyUnicode_AsUTF8(cur_frame->f_code->co_name)
+ fnum,
+ PyUnicode_AsUTF8(cur_frame->f_code->co_filename),
+ PyFrame_GetLineNumber(cur_frame),
+ PyUnicode_AsUTF8(cur_frame->f_code->co_name)
);
ptb = ptb->tb_next;
{
int i;
Py_ssize_t tuple_len;
- tmpl_t *dst;
+ tmpl_t *dst;
fr_pair_t *vp;
- request_t *current = request;
- fr_pair_list_t tmp_list;
+ request_t *current = request;
- fr_pair_list_init(&tmp_list);
/*
* If the Python function gave us None for the tuple,
* then just return.
continue;
}
- if (tmpl_request_ptr(¤t, tmpl_request(dst)) < 0) {
+ if (radius_request(¤t, tmpl_request(dst)) < 0) {
ERROR("%s - Attribute name %s.%s refers to outer request but not in a tunnel, skipping...",
funcname, list_name, s1);
talloc_free(dst);
fr_table_str_by_value(fr_tokens_table, op, "="), s2);
}
- fr_pair_add(&tmp_list, vp);
+ radius_pairmove(current, vps, &vp, false);
}
- radius_pairmove(request, vps, &tmp_list, false);
}
return 0;
}
+static inline CC_HINT(always_inline) PyObject *pair_list_alloc(request_t *request, tmpl_pair_list_t list_ref)
+{
+ PyObject *py_list;
+ py_freeradius_pair_list_t *our_list;
+
+ py_list = PyObject_CallObject((PyObject *)&py_freeradius_leagcy_pair_list_def, NULL);
+ if (unlikely(!py_list)) return NULL;
+
+ our_list = (py_freeradius_pair_list_t *)py_list;
+ our_list->list_ref = list_ref;
+ our_list->pair.head = tmpl_request_pair_list(request, list_ref);
+ return py_list;
+}
+
static unlang_action_t do_python_single(rlm_rcode_t *p_result,
rlm_python_t const *inst, request_t *request, PyObject *p_func, char const *funcname)
{
- fr_pair_t *vp;
- PyObject *p_ret = NULL;
- PyObject *p_arg = NULL;
- int tuple_len;
- rlm_rcode_t rcode = RLM_MODULE_OK;
+ PyObject *p_ret = NULL;
+ PyObject *p_arg = NULL;
+
+ PyObject *py_request;
+ py_freeradius_request_t *our_request;
+
+ PyObject *py_pair_root;
+ py_freeradius_pair_root_t *our_pair_root;
+
+ rlm_rcode_t rcode = RLM_MODULE_OK;
+
+ rlm_python_set_request(request);
/*
- * We will pass a tuple containing (name, value) tuples
- * We can safely use the Python function to build up a
- * tuple, since the tuple is not used elsewhere.
- *
- * Determine the size of our tuple by walking through the packet.
- * If request is NULL, pass None.
+ * Instantiate the request
*/
- tuple_len = 0;
- if (request != NULL) {
- tuple_len = fr_pair_list_len(&request->request_pairs);
+ py_request = PyObject_CallObject((PyObject *)&py_freeradius_request_def, NULL);
+ if (unlikely(!py_request)) {
+ python_error_log(inst, request);
+ RETURN_MODULE_FAIL;
}
+ our_request = (py_freeradius_request_t *)py_request;
+ our_request->request = request;
- if (tuple_len == 0) {
- Py_INCREF(Py_None);
- p_arg = Py_None;
- } else {
- int i = 0;
- if ((p_arg = PyTuple_New(tuple_len)) == NULL) {
- rcode = RLM_MODULE_FAIL;
- goto finish;
- }
+ /*
+ * Instantiate the pair root
+ */
+ py_pair_root = PyObject_CallObject((PyObject *)&py_freeradius_pair_root_def, NULL);
+ if (unlikely(!py_pair_root)) {
+ req_error:
+ Py_DECREF(py_request);
+ python_error_log(inst, request);
+ RETURN_MODULE_FAIL;
+ }
+ our_pair_root = (py_freeradius_pair_root_t *)py_pair_root;
+ our_request->pairs = py_pair_root;
- for (vp = fr_pair_list_head(&request->request_pairs);
- vp;
- vp = fr_pair_list_next(&request->request_pairs, vp), i++) {
- PyObject *pp;
+ /*
+ * Create the actual list roots
+ * This may be removed when we have a single
+ * pair root as it's not very efficient.
+ *
+ * This is the reason we have a pairs object
+ * above the pair lists.
+ */
+ our_pair_root->request = pair_list_alloc(request, PAIR_LIST_REQUEST);
+ if (unlikely(!our_pair_root->request)) goto req_error;
- /* The inside tuple has two only: */
- if ((pp = PyTuple_New(2)) == NULL) {
- rcode = RLM_MODULE_FAIL;
- goto finish;
- }
+ our_pair_root->reply = pair_list_alloc(request, PAIR_LIST_REPLY);
+ if (unlikely(!our_pair_root->reply)) goto req_error;
- if (mod_populate_vptuple(inst, request, pp, vp) == 0) {
- /* Put the tuple inside the container */
- PyTuple_SET_ITEM(p_arg, i, pp);
- } else {
- Py_INCREF(Py_None);
- PyTuple_SET_ITEM(p_arg, i, Py_None);
- Py_DECREF(pp);
- }
- }
- }
+ our_pair_root->control = pair_list_alloc(request, PAIR_LIST_CONTROL);
+ if (unlikely(!our_pair_root->control)) goto req_error;
+
+ our_pair_root->state = pair_list_alloc(request, PAIR_LIST_STATE);
+ if (unlikely(!our_pair_root->state)) goto req_error;
/* Call Python function. */
- p_ret = PyObject_CallFunctionObjArgs(p_func, p_arg, NULL);
+ p_ret = PyObject_CallFunctionObjArgs(p_func, py_request, NULL);
if (!p_ret) {
python_error_log(inst, request); /* Needs valid thread with GIL */
rcode = RLM_MODULE_FAIL;
/* Now have the return value */
rcode = PyLong_AsLong(p_tuple_int);
/* Reply item tuple */
- mod_vptuple(request->reply_ctx, inst, request, &request->reply_pairs,
+ mod_vptuple(request->reply, inst, request, &request->reply_pairs,
PyTuple_GET_ITEM(p_ret, 1), funcname, "reply");
/* Config item tuple */
- mod_vptuple(request->control_ctx, inst, request, &request->control_pairs,
+ mod_vptuple(request, inst, request, &request->control_pairs,
PyTuple_GET_ITEM(p_ret, 2), funcname, "config");
} else if (PyNumber_Check(p_ret)) {
}
finish:
+ rlm_python_set_request(NULL);
+
if (rcode == RLM_MODULE_FAIL) python_error_log(inst, request);
Py_XDECREF(p_arg);
Py_XDECREF(p_ret);
*/
static PyObject *python_module_init(void)
{
- rlm_python_t *inst = current_inst;
- PyObject *module;
+ rlm_python_t *inst = current_inst;
+ PyObject *module;
+ PyObject *p_state;
- static struct PyModuleDef py_module_def = {
- PyModuleDef_HEAD_INIT,
- .m_name = "freeradius",
- .m_doc = "freeRADIUS python module",
- .m_size = -1,
- .m_methods = module_methods
- };
+ static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER;
+ bool type_ready = false;
fr_assert(inst);
- module = PyModule_Create(&py_module_def);
+ /*
+ * Only allow one thread at a time do the module
+ * init. This is out of an abundance of caution
+ * as it's unclear whether the reference counts
+ * on the various objects are thread safe.
+ */
+ pthread_mutex_lock(&init_lock);
+
+ /*
+ * The type definitions are global, so we only
+ * need to call the init functions the first
+ * pass through.
+ */
+ if (!type_ready) {
+ /*
+ * We need to initialise the definitions first
+ * this fills in any fields we didn't explicitly
+ * specify, and gets the structures ready for
+ * use by the python interpreter.
+ */
+ if (PyType_Ready(&py_freeradius_pair_def) < 0) {
+ error:
+ abort();
+ pthread_mutex_unlock(&init_lock);
+ python_error_log(inst, NULL);
+ Py_RETURN_NONE;
+ }
+
+ if (PyType_Ready(&py_freeradius_leagcy_pair_list_def) < 0) goto error;
+
+ if (PyType_Ready(&py_freeradius_pair_root_def) < 0) goto error;
+
+ if (PyType_Ready(&py_freeradius_request_def) < 0) goto error;
+
+ if (PyType_Ready(&py_freeradius_state_def) < 0) goto error;
+
+ type_ready = true;
+ }
+
+ /*
+ * The module is per-interpreter
+ */
+ module = PyModule_Create(&py_freeradius_def);
if (!module) {
- python_error_log(inst, NULL);
- Py_RETURN_NONE;
+ Py_DECREF(module);
+ goto error;
+ }
+
+ /*
+ * PyModule_AddObject steals ref on success, we we
+ * INCREF here to give it something to steal, else
+ * on free the refcount would go negative.
+ *
+ * Note here we're creating a new instance of an
+ * object, not adding the object definition itself
+ * as there's no reason that a python script would
+ * ever need to create an instance object.
+ *
+ * The instantiation function associated with the
+ * the __State object takes care of populating the
+ * instance data from globals and thread-specific
+ * variables.
+ */
+ p_state = PyObject_CallObject((PyObject *)&py_freeradius_state_def, NULL);
+ Py_INCREF(&py_freeradius_state_def);
+
+ if (PyModule_AddObject(module, "__State", p_state) < 0) {
+ Py_DECREF(&py_freeradius_state_def);
+ Py_DECREF(module);
+ goto error;
}
+ /*
+ * For "Pair" we're inserting an object definition
+ * as opposed to the object instance we inserted
+ * for inst.
+ */
+ Py_INCREF(&py_freeradius_pair_def);
+ if (PyModule_AddObject(module, "Pair", (PyObject *)&py_freeradius_pair_def) < 0) {
+ Py_DECREF(&py_freeradius_pair_def);
+ Py_DECREF(module);
+ goto error;
+ }
+ pthread_mutex_unlock(&init_lock);
+
return module;
}
return 0;
}
-static void python_interpreter_free(rlm_python_t *inst, PyThreadState *interp)
+static void python_interpreter_free(UNUSED rlm_python_t *inst, PyThreadState *interp)
{
/*
* We incremented the reference count earlier
static int mod_thread_instantiate(UNUSED CONF_SECTION const *conf, void *instance,
UNUSED fr_event_list_t *el, void *thread)
{
- PyThreadState *state;
- rlm_python_t *inst = instance;
- rlm_python_thread_t *this_thread = thread;
+ rlm_python_t *inst = talloc_get_type_abort(instance, rlm_python_t);
+ rlm_python_thread_t *t = talloc_get_type_abort(thread, rlm_python_thread_t);
+
+ PyThreadState *t_state;
+ PyObject *t_dict;
+ PyObject *p_state;
- state = PyThreadState_New(inst->interpreter->interp);
- if (!state) {
+ current_t = t;
+
+ t_state = PyThreadState_New(inst->interpreter->interp);
+ if (unlikely(!t_state)) {
ERROR("Failed initialising local PyThreadState");
return -1;
}
- DEBUG3("Initialised new thread state %p", state);
- this_thread->state = state;
+ PyEval_RestoreThread(t_state); /* Switches thread state and locks GIL */
+ t_dict = PyThreadState_GetDict();
+ if (unlikely(!t_dict)) {
+ ERROR("Failed getting PyThreadState dictionary");
+ error:
+ PyEval_SaveThread(); /* Unlock GIL */
+ PyThreadState_Delete(t_state);
+
+ return -1;
+ }
+
+ /*
+ * Instantiate a new instance object which captures
+ * the global and thread instances, and associates
+ * them with the thread.
+ */
+ p_state = PyObject_CallObject((PyObject *)&py_freeradius_state_def, NULL);
+ if (unlikely(!p_state)) {
+ ERROR("Failed instantiating module instance information object");
+ goto error;
+ }
+
+ if (unlikely(PyDict_SetItemString(t_dict, "__State", p_state) < 0)) {
+ ERROR("Failed setting module instance information in thread dict");
+ goto error;
+ }
+
+ DEBUG3("Initialised PyThreadState %p", t_state);
+ t->state = t_state;
+ PyEval_SaveThread(); /* Unlock GIL */
return 0;
}
.type = RLM_TYPE_THREAD_SAFE,
.inst_size = sizeof(rlm_python_t),
+ .inst_type = "rlm_python_t",
.thread_inst_size = sizeof(rlm_python_thread_t),
+ .thread_inst_type = "rlm_python_thread_t",
.config = module_config,
.onload = mod_load,