]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Python v4 nesting python-v4-rework
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 4 Feb 2021 10:57:40 +0000 (10:57 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 4 Feb 2021 10:58:14 +0000 (10:58 +0000)
raddb/mods-available/python
raddb/sites-available/default
src/modules/rlm_python/example.py
src/modules/rlm_python/rlm_python.c

index fad7008fd10afb006d6beb40549a0486b8fd68cb..dfee4caa0744791290f5b7967cfc51f717d6a43c 100644 (file)
@@ -66,7 +66,7 @@ python {
        #  The search path for Python modules.  It must include the path to your
        #  Python module.
        #
-#      python_path = ${modconfdir}/${.:name}
+       python_path = ${modconfdir}/${.:name}
 
        #
        #  python_path_include_conf_dir::
@@ -104,7 +104,7 @@ python {
        #
 #      func_detach = detach
 
-#      func_authorize = authorize
+       func_authorize = authorize
 #      func_authenticate = authenticate
 #      func_preacct = preacct
 #      func_accounting = accounting
index 6a0a5967940f271203b13a7dbae1786eae2e2eac..9d87df39dccf314e9e7cd85d88c03a74b9c607ba 100644 (file)
-#  -*- 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`
@@ -1588,4 +737,6 @@ send Accounting-Response {
        #
        attr_filter.accounting_response
 }
+=======
+>>>>>>> Stashed changes
 }
index 8ff58f269253e2f64d2e8221b9a9f3190dfd7174..1f37eac1a18acadec0f9e4f188aa272a09bef280 100644 (file)
@@ -12,12 +12,13 @@ def instantiate(p):
   print(p)
   # return 0 for success or -1 for failure
 
-def authorize(p):
+def authorize(request):
   print("*** authorize ***")
+  
+  freeradius.log('*** log call in authorize ***')
   print("")
-  freeradius.log(freeradius.L_INFO, '*** log call in authorize ***')
-  print("")
-  print(p)
+  print(request)
+  print(request.pairs.request)
   print("")
   print(freeradius.config)
   print("")
@@ -30,7 +31,7 @@ def preacct(p):
 
 def accounting(p):
   print("*** accounting ***")
-  freeradius.log(freeradius.L_INFO, '*** log call in accounting (0) ***')
+  freeradius.log('*** log call in accounting (0) ***')
   print("")
   print(p)
   return freeradius.RLM_MODULE_OK
index 400f280bf810e9bab6b14c8a48417d8d4a776d2a..59be71c9cc6e5d08cfbb724d9fc79e9f17ded4a6 100644 (file)
  * @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.
  */
@@ -37,9 +39,12 @@ RCSID("$Id$")
 #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
  *
@@ -84,33 +89,486 @@ typedef struct {
  * 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.
@@ -145,14 +603,15 @@ static struct {
 #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)
@@ -169,41 +628,11 @@ static struct {
        { 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;
 
@@ -223,10 +652,10 @@ static void python_error_log(const rlm_python_t *inst, request_t *request)
                        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;
@@ -247,12 +676,10 @@ static void mod_vptuple(TALLOC_CTX *ctx, rlm_python_t const *inst, request_t *re
 {
        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.
@@ -333,7 +760,7 @@ static void mod_vptuple(TALLOC_CTX *ctx, rlm_python_t const *inst, request_t *re
                        continue;
                }
 
-               if (tmpl_request_ptr(&current, tmpl_request(dst)) < 0) {
+               if (radius_request(&current, 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);
@@ -352,9 +779,8 @@ static void mod_vptuple(TALLOC_CTX *ctx, rlm_python_t const *inst, request_t *re
                              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);
 }
 
 
@@ -465,62 +891,82 @@ static int mod_populate_vptuple(rlm_python_t const *inst, request_t *request, Py
        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;
@@ -564,10 +1010,10 @@ static unlang_action_t do_python_single(rlm_rcode_t *p_result,
                /* 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)) {
@@ -585,6 +1031,8 @@ static unlang_action_t do_python_single(rlm_rcode_t *p_result,
        }
 
 finish:
+       rlm_python_set_request(NULL);
+
        if (rcode == RLM_MODULE_FAIL) python_error_log(inst, request);
        Py_XDECREF(p_arg);
        Py_XDECREF(p_ret);
@@ -848,25 +1296,100 @@ static char *python_path_build(TALLOC_CTX *ctx, rlm_python_t *inst, CONF_SECTION
  */
 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;
 }
 
@@ -925,7 +1448,7 @@ static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf)
        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
@@ -1053,18 +1576,51 @@ static int mod_detach(void *instance)
 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;
 }
@@ -1170,7 +1726,9 @@ module_t rlm_python = {
        .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,