pyrad2 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrad2-1.0.0/LICENSE.txt +31 -0
- pyrad2-1.0.0/PKG-INFO +63 -0
- pyrad2-1.0.0/README.md +47 -0
- pyrad2-1.0.0/pyproject.toml +24 -0
- pyrad2-1.0.0/pyrad2/__init__.py +45 -0
- pyrad2-1.0.0/pyrad2/bidict.py +53 -0
- pyrad2-1.0.0/pyrad2/client.py +242 -0
- pyrad2-1.0.0/pyrad2/client_async.py +446 -0
- pyrad2-1.0.0/pyrad2/dictfile.py +113 -0
- pyrad2-1.0.0/pyrad2/dictionary.py +476 -0
- pyrad2-1.0.0/pyrad2/host.py +98 -0
- pyrad2-1.0.0/pyrad2/packet.py +1040 -0
- pyrad2-1.0.0/pyrad2/proxy.py +70 -0
- pyrad2-1.0.0/pyrad2/server.py +354 -0
- pyrad2-1.0.0/pyrad2/server_async.py +298 -0
- pyrad2-1.0.0/pyrad2/tools.py +297 -0
- pyrad2-1.0.0/pyrad2.egg-info/PKG-INFO +63 -0
- pyrad2-1.0.0/pyrad2.egg-info/SOURCES.txt +32 -0
- pyrad2-1.0.0/pyrad2.egg-info/dependency_links.txt +1 -0
- pyrad2-1.0.0/pyrad2.egg-info/requires.txt +1 -0
- pyrad2-1.0.0/pyrad2.egg-info/top_level.txt +1 -0
- pyrad2-1.0.0/pyrad2.egg-info/zip-safe +1 -0
- pyrad2-1.0.0/setup.cfg +8 -0
- pyrad2-1.0.0/setup.py +30 -0
- pyrad2-1.0.0/tests/test_bidict.py +56 -0
- pyrad2-1.0.0/tests/test_client.py +193 -0
- pyrad2-1.0.0/tests/test_dictionary.py +369 -0
- pyrad2-1.0.0/tests/test_host.py +87 -0
- pyrad2-1.0.0/tests/test_packet.py +645 -0
- pyrad2-1.0.0/tests/test_proxy.py +98 -0
- pyrad2-1.0.0/tests/test_server.py +341 -0
- pyrad2-1.0.0/tests/test_server_async.py +110 -0
- pyrad2-1.0.0/tests/test_tools.py +104 -0
pyrad2-1.0.0/LICENSE.txt
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Copyright 2020 Istvan Ruzman. All rights reserved.
|
|
2
|
+
Copyright 2017-2023 Christian Giese. All rights reserved.
|
|
3
|
+
Copyright 2007-2008 Simplon. All rights reserved.
|
|
4
|
+
Copyright 2002-2008 Wichert Akkerman. All rights reserved.
|
|
5
|
+
|
|
6
|
+
All rights reserved.
|
|
7
|
+
|
|
8
|
+
Redistribution and use in source and binary forms, with or without
|
|
9
|
+
modification, are permitted provided that the following conditions
|
|
10
|
+
are met:
|
|
11
|
+
1. Redistributions of source code must retain the above copyright
|
|
12
|
+
notice, this list of conditions and the following disclaimer.
|
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
14
|
+
notice, this list of conditions and the following disclaimer in the
|
|
15
|
+
documentation and/or other materials provided with the distribution.
|
|
16
|
+
3. Neither the name of the University nor the names of its contributors
|
|
17
|
+
may be used to endorse or promote products derived from this software
|
|
18
|
+
without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
21
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
23
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
26
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
27
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
28
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
29
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
30
|
+
SUCH DAMAGE.
|
|
31
|
+
|
pyrad2-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyrad2
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: RADIUS Server
|
|
5
|
+
Home-page: https://github.com/nicholasamorim/pyrad2
|
|
6
|
+
Author: Nicholas Amorim, Istvan Ruzman, Christian Giese
|
|
7
|
+
Author-email: nicholas@santos.ee, istvan@ruzman.eu, developer@gicnet.de
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Requires-Dist: loguru>=0.7.3
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: home-page
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
[](https://github.com/miraclesupernova/stickystack/actions/workflows/django.yml)
|
|
18
|
+
[](https://www.python.org)
|
|
19
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
20
|
+
[]([https://github.com/psf/black](https://github.com/astral-sh/uv))
|
|
21
|
+
[](http://mypy-lang.org/)
|
|
22
|
+
|
|
23
|
+
pyrad2 is an implementation of a RADIUS client/server as described in RFC2865. It takes care of all the details like building RADIUS packets,
|
|
24
|
+
sending them and decoding responses.
|
|
25
|
+
|
|
26
|
+
# Introduction
|
|
27
|
+
|
|
28
|
+
This is a fork of [pyrad](https://github.com/pyradius/pyrad) aiming to make it compatible with Python 3.12+ and introduce bug fixes and features. Most of the codebase now has type checking and test coverage has been increased. Legacy compatibility code with (very) older versions of Python have been removed and we only support Python 3.12+.
|
|
29
|
+
|
|
30
|
+
Note that this is _not_ a stand-alone Radius implementation like [FreeRadius](https://www.freeradius.org). You are supposed to inherit the server classes and code your own behind-the-scenes implementation. This package allows you to code your business logic on top of it.
|
|
31
|
+
|
|
32
|
+
# Requirements & Installation
|
|
33
|
+
|
|
34
|
+
pyrad2 requires Python 3.12 and uses [uv](https://github.com/astral-sh/uv). On a Mac, you can simply run `brew install uv`.
|
|
35
|
+
|
|
36
|
+
# Examples
|
|
37
|
+
|
|
38
|
+
There are a few examples in the `examples` folder.
|
|
39
|
+
|
|
40
|
+
The easiest way to start a server is by running `make test_server_async`. This will run the example server in `examples/server_async.py`.
|
|
41
|
+
|
|
42
|
+
If you want to see a request in action, leave the server running, open another terminal and type `make test_auth`.
|
|
43
|
+
|
|
44
|
+
# Tests
|
|
45
|
+
|
|
46
|
+
Run `make test`.
|
|
47
|
+
|
|
48
|
+
# Author, Copyright, Availability
|
|
49
|
+
|
|
50
|
+
pyrad2 is currently maintaned by Nicholas Amorim \<<nicholas@santos.ee\>.
|
|
51
|
+
|
|
52
|
+
pyrad was written by Wichert Akkerman \<<wichert@wiggy.net>\> and is
|
|
53
|
+
maintained by Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
|
|
54
|
+
|
|
55
|
+
This project is licensed under a BSD license.
|
|
56
|
+
|
|
57
|
+
Copyright and license information can be found in the LICENSE.txt file.
|
|
58
|
+
|
|
59
|
+
The current version and documentation can be found on pypi:
|
|
60
|
+
<https://pypi.org/project/pyrad2/>
|
|
61
|
+
|
|
62
|
+
Bugs and wishes can be submitted in the pyrad issue tracker on github:
|
|
63
|
+
<https://github.com/nicholasamorim/pyrad2/issues>
|
pyrad2-1.0.0/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[](https://github.com/miraclesupernova/stickystack/actions/workflows/django.yml)
|
|
2
|
+
[](https://www.python.org)
|
|
3
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
4
|
+
[]([https://github.com/psf/black](https://github.com/astral-sh/uv))
|
|
5
|
+
[](http://mypy-lang.org/)
|
|
6
|
+
|
|
7
|
+
pyrad2 is an implementation of a RADIUS client/server as described in RFC2865. It takes care of all the details like building RADIUS packets,
|
|
8
|
+
sending them and decoding responses.
|
|
9
|
+
|
|
10
|
+
# Introduction
|
|
11
|
+
|
|
12
|
+
This is a fork of [pyrad](https://github.com/pyradius/pyrad) aiming to make it compatible with Python 3.12+ and introduce bug fixes and features. Most of the codebase now has type checking and test coverage has been increased. Legacy compatibility code with (very) older versions of Python have been removed and we only support Python 3.12+.
|
|
13
|
+
|
|
14
|
+
Note that this is _not_ a stand-alone Radius implementation like [FreeRadius](https://www.freeradius.org). You are supposed to inherit the server classes and code your own behind-the-scenes implementation. This package allows you to code your business logic on top of it.
|
|
15
|
+
|
|
16
|
+
# Requirements & Installation
|
|
17
|
+
|
|
18
|
+
pyrad2 requires Python 3.12 and uses [uv](https://github.com/astral-sh/uv). On a Mac, you can simply run `brew install uv`.
|
|
19
|
+
|
|
20
|
+
# Examples
|
|
21
|
+
|
|
22
|
+
There are a few examples in the `examples` folder.
|
|
23
|
+
|
|
24
|
+
The easiest way to start a server is by running `make test_server_async`. This will run the example server in `examples/server_async.py`.
|
|
25
|
+
|
|
26
|
+
If you want to see a request in action, leave the server running, open another terminal and type `make test_auth`.
|
|
27
|
+
|
|
28
|
+
# Tests
|
|
29
|
+
|
|
30
|
+
Run `make test`.
|
|
31
|
+
|
|
32
|
+
# Author, Copyright, Availability
|
|
33
|
+
|
|
34
|
+
pyrad2 is currently maintaned by Nicholas Amorim \<<nicholas@santos.ee\>.
|
|
35
|
+
|
|
36
|
+
pyrad was written by Wichert Akkerman \<<wichert@wiggy.net>\> and is
|
|
37
|
+
maintained by Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
|
|
38
|
+
|
|
39
|
+
This project is licensed under a BSD license.
|
|
40
|
+
|
|
41
|
+
Copyright and license information can be found in the LICENSE.txt file.
|
|
42
|
+
|
|
43
|
+
The current version and documentation can be found on pypi:
|
|
44
|
+
<https://pypi.org/project/pyrad2/>
|
|
45
|
+
|
|
46
|
+
Bugs and wishes can be submitted in the pyrad issue tracker on github:
|
|
47
|
+
<https://github.com/nicholasamorim/pyrad2/issues>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyrad2"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "RADIUS Server"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"loguru>=0.7.3",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[dependency-groups]
|
|
12
|
+
dev = [
|
|
13
|
+
"mypy>=1.16.1",
|
|
14
|
+
"pytest>=8.4.1",
|
|
15
|
+
"pytest-cov>=6.2.1",
|
|
16
|
+
"pytest-sugar>=1.0.0",
|
|
17
|
+
"pyupgrade>=3.20.0",
|
|
18
|
+
"ruff>=0.12.2",
|
|
19
|
+
"types-setuptools>=80.9.0.20250529",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# [build-system]
|
|
23
|
+
# requires = ["setuptools>=61.0", "wheel"]
|
|
24
|
+
# build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Python RADIUS client code.
|
|
2
|
+
|
|
3
|
+
pyrad is an implementation of a RADIUS client as described in RFC2865.
|
|
4
|
+
It takes care of all the details like building RADIUS packets, sending
|
|
5
|
+
them and decoding responses.
|
|
6
|
+
|
|
7
|
+
Here is an example of doing a authentication request::
|
|
8
|
+
|
|
9
|
+
import pyrad.packet
|
|
10
|
+
from pyrad.client import Client
|
|
11
|
+
from pyrad.dictionary import Dictionary
|
|
12
|
+
|
|
13
|
+
srv = Client(server="radius.my.domain", secret="s3cr3t",
|
|
14
|
+
dict = Dictionary("dicts/dictionary", "dictionary.acc"))
|
|
15
|
+
|
|
16
|
+
req = srv.CreatePacket(code=pyrad.packet.AccessRequest,
|
|
17
|
+
User_Name = "wichert", NAS_Identifier="localhost")
|
|
18
|
+
req["User-Password"] = req.PwCrypt("password")
|
|
19
|
+
|
|
20
|
+
reply = srv.SendPacket(req)
|
|
21
|
+
if reply.code = =pyrad.packet.AccessAccept:
|
|
22
|
+
print "access accepted"
|
|
23
|
+
else:
|
|
24
|
+
print "access denied"
|
|
25
|
+
|
|
26
|
+
print "Attributes returned by server:"
|
|
27
|
+
for i in reply.keys():
|
|
28
|
+
print "%s: %s" % (i, reply[i])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
This package contains four modules:
|
|
32
|
+
|
|
33
|
+
- client: RADIUS client code
|
|
34
|
+
- dictionary: RADIUS attribute dictionary
|
|
35
|
+
- packet: a RADIUS packet as send to/from servers
|
|
36
|
+
- tools: utility functions
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
__docformat__ = "epytext en"
|
|
40
|
+
|
|
41
|
+
__author__ = "Nicholas Amorim <nicholas@bloomshield.ee>"
|
|
42
|
+
__url__ = "http://pyrad2.readthedocs.io/en/latest/?badge=latest"
|
|
43
|
+
__version__ = "1.0"
|
|
44
|
+
|
|
45
|
+
__all__ = ["client", "dictionary", "packet", "server", "tools", "dictfile"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Any, Dict, Hashable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BiDict:
|
|
5
|
+
"""
|
|
6
|
+
BiDict (Bidirectional Dictionary) provides a one-to-one mapping
|
|
7
|
+
between keys and values.
|
|
8
|
+
|
|
9
|
+
Supports both forward and reverse lookup.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
"""Initialize empty forward and reverse dictionaries."""
|
|
14
|
+
self.forward: Dict[Hashable, Any] = {}
|
|
15
|
+
self.backward: Dict[Hashable, Any] = {}
|
|
16
|
+
|
|
17
|
+
def Add(self, one: Hashable, two: Hashable) -> None:
|
|
18
|
+
"""Add a bidirectional mapping between 'one' and 'two'."""
|
|
19
|
+
self.forward[one] = two
|
|
20
|
+
self.backward[two] = one
|
|
21
|
+
|
|
22
|
+
def __len__(self) -> int:
|
|
23
|
+
"""Return the number of entries in the dictionary."""
|
|
24
|
+
return len(self.forward)
|
|
25
|
+
|
|
26
|
+
def __getitem__(self, key: Hashable) -> Any:
|
|
27
|
+
"""Retrieve the value associated with the given key."""
|
|
28
|
+
return self.GetForward(key)
|
|
29
|
+
|
|
30
|
+
def __delitem__(self, key: Hashable) -> None:
|
|
31
|
+
"""Remove key and its associated value from the dictionary."""
|
|
32
|
+
if key in self.forward:
|
|
33
|
+
del self.backward[self.forward[key]]
|
|
34
|
+
del self.forward[key]
|
|
35
|
+
else:
|
|
36
|
+
del self.forward[self.backward[key]]
|
|
37
|
+
del self.backward[key]
|
|
38
|
+
|
|
39
|
+
def GetForward(self, key: Hashable) -> Any:
|
|
40
|
+
"""Return the value associated with 'key' from the forward mapping."""
|
|
41
|
+
return self.forward[key]
|
|
42
|
+
|
|
43
|
+
def HasForward(self, key: Hashable) -> bool:
|
|
44
|
+
"""Check if 'key' exists in the forward mapping."""
|
|
45
|
+
return key in self.forward
|
|
46
|
+
|
|
47
|
+
def GetBackward(self, key: Hashable) -> Any:
|
|
48
|
+
"""Return the key associated with 'value' from the reverse mapping."""
|
|
49
|
+
return self.backward[key]
|
|
50
|
+
|
|
51
|
+
def HasBackward(self, key: Hashable) -> bool:
|
|
52
|
+
"""Check if 'value' exists in the reverse mapping."""
|
|
53
|
+
return key in self.backward
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
__docformat__ = "epytext en"
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import select
|
|
5
|
+
import socket
|
|
6
|
+
import struct
|
|
7
|
+
import time
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from pyrad2 import host, packet
|
|
11
|
+
from pyrad2.dictionary import Dictionary
|
|
12
|
+
|
|
13
|
+
EAP_CODE_REQUEST = 1
|
|
14
|
+
EAP_CODE_RESPONSE = 2
|
|
15
|
+
EAP_TYPE_IDENTITY = 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Timeout(Exception):
|
|
19
|
+
"""Simple exception class which is raised when a timeout occurs
|
|
20
|
+
while waiting for a RADIUS server to respond."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Client(host.Host):
|
|
24
|
+
"""Basic RADIUS client.
|
|
25
|
+
This class implements a basic RADIUS client. It can send requests
|
|
26
|
+
to a RADIUS server, taking care of timeouts and retries, and
|
|
27
|
+
validate its replies.
|
|
28
|
+
|
|
29
|
+
:ivar retries: number of times to retry sending a RADIUS request
|
|
30
|
+
:type retries: integer
|
|
31
|
+
:ivar timeout: number of seconds to wait for an answer
|
|
32
|
+
:type timeout: float
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
server: str,
|
|
38
|
+
authport: int = 1812,
|
|
39
|
+
acctport: int = 1813,
|
|
40
|
+
coaport: int = 3799,
|
|
41
|
+
secret: bytes = b"",
|
|
42
|
+
dict: Optional[Dictionary] = None,
|
|
43
|
+
retries: int = 3,
|
|
44
|
+
timeout: int = 5,
|
|
45
|
+
):
|
|
46
|
+
"""Constructor.
|
|
47
|
+
|
|
48
|
+
:param server: hostname or IP address of RADIUS server
|
|
49
|
+
:type server: string
|
|
50
|
+
:param authport: port to use for authentication packets
|
|
51
|
+
:type authport: integer
|
|
52
|
+
:param acctport: port to use for accounting packets
|
|
53
|
+
:type acctport: integer
|
|
54
|
+
:param coaport: port to use for CoA packets
|
|
55
|
+
:type coaport: integer
|
|
56
|
+
:param secret: RADIUS secret
|
|
57
|
+
:type secret: string
|
|
58
|
+
:param dict: RADIUS dictionary
|
|
59
|
+
:type dict: pyrad.dictionary.Dictionary
|
|
60
|
+
"""
|
|
61
|
+
super().__init__(authport, acctport, coaport, dict)
|
|
62
|
+
|
|
63
|
+
self.server = server
|
|
64
|
+
self.secret = secret
|
|
65
|
+
self.retries = retries
|
|
66
|
+
self.timeout = timeout
|
|
67
|
+
self._poll = select.poll()
|
|
68
|
+
self._socket: Optional[socket.socket] = None
|
|
69
|
+
|
|
70
|
+
def bind(self, addr: str) -> None:
|
|
71
|
+
"""Bind socket to an address.
|
|
72
|
+
Binding the socket used for communicating to an address can be
|
|
73
|
+
usefull when working on a machine with multiple addresses.
|
|
74
|
+
|
|
75
|
+
:param addr: network address (hostname or IP) and port to bind to
|
|
76
|
+
:type addr: host,port tuple
|
|
77
|
+
"""
|
|
78
|
+
self._CloseSocket()
|
|
79
|
+
self._SocketOpen()
|
|
80
|
+
if self._socket:
|
|
81
|
+
self._socket.bind(addr)
|
|
82
|
+
else:
|
|
83
|
+
raise RuntimeError("No socket present")
|
|
84
|
+
|
|
85
|
+
def _SocketOpen(self) -> None:
|
|
86
|
+
try:
|
|
87
|
+
family = socket.getaddrinfo(self.server, 80)[0][0]
|
|
88
|
+
except Exception:
|
|
89
|
+
family = socket.AF_INET
|
|
90
|
+
if not self._socket:
|
|
91
|
+
self._socket = socket.socket(family, socket.SOCK_DGRAM)
|
|
92
|
+
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
93
|
+
self._poll.register(self._socket, select.POLLIN)
|
|
94
|
+
|
|
95
|
+
def _CloseSocket(self) -> None:
|
|
96
|
+
if self._socket:
|
|
97
|
+
self._poll.unregister(self._socket)
|
|
98
|
+
self._socket.close()
|
|
99
|
+
self._socket = None
|
|
100
|
+
|
|
101
|
+
def CreateAuthPacket(self, **args) -> packet.Packet:
|
|
102
|
+
"""Create a new RADIUS packet.
|
|
103
|
+
This utility function creates a new RADIUS packet which can
|
|
104
|
+
be used to communicate with the RADIUS server this client
|
|
105
|
+
talks to. This is initializing the new packet with the
|
|
106
|
+
dictionary and secret used for the client.
|
|
107
|
+
|
|
108
|
+
:return: a new empty packet instance
|
|
109
|
+
:rtype: pyrad.packet.AuthPacket
|
|
110
|
+
"""
|
|
111
|
+
return super().CreateAuthPacket(secret=self.secret, **args)
|
|
112
|
+
|
|
113
|
+
def CreateAcctPacket(self, **args) -> packet.Packet:
|
|
114
|
+
"""Create a new RADIUS packet.
|
|
115
|
+
This utility function creates a new RADIUS packet which can
|
|
116
|
+
be used to communicate with the RADIUS server this client
|
|
117
|
+
talks to. This is initializing the new packet with the
|
|
118
|
+
dictionary and secret used for the client.
|
|
119
|
+
|
|
120
|
+
:return: a new empty packet instance
|
|
121
|
+
:rtype: pyrad.packet.Packet
|
|
122
|
+
"""
|
|
123
|
+
return super().CreateAcctPacket(secret=self.secret, **args)
|
|
124
|
+
|
|
125
|
+
def CreateCoAPacket(self, **args) -> packet.Packet:
|
|
126
|
+
"""Create a new RADIUS packet.
|
|
127
|
+
This utility function creates a new RADIUS packet which can
|
|
128
|
+
be used to communicate with the RADIUS server this client
|
|
129
|
+
talks to. This is initializing the new packet with the
|
|
130
|
+
dictionary and secret used for the client.
|
|
131
|
+
|
|
132
|
+
:return: a new empty packet instance
|
|
133
|
+
:rtype: pyrad.packet.Packet
|
|
134
|
+
"""
|
|
135
|
+
return super().CreateCoAPacket(secret=self.secret, **args)
|
|
136
|
+
|
|
137
|
+
def _SendPacket(self, pkt: packet.PacketImplementation, port: int):
|
|
138
|
+
"""Send a packet to a RADIUS server.
|
|
139
|
+
|
|
140
|
+
:param pkt: the packet to send
|
|
141
|
+
:type pkt: pyrad.packet.Packet
|
|
142
|
+
:param port: UDP port to send packet to
|
|
143
|
+
:type port: integer
|
|
144
|
+
:return: the reply packet received
|
|
145
|
+
:rtype: pyrad.packet.Packet
|
|
146
|
+
:raise Timeout: RADIUS server does not reply
|
|
147
|
+
"""
|
|
148
|
+
self._SocketOpen()
|
|
149
|
+
|
|
150
|
+
for attempt in range(self.retries):
|
|
151
|
+
if attempt and pkt.code == packet.AccountingRequest:
|
|
152
|
+
if "Acct-Delay-Time" in pkt:
|
|
153
|
+
pkt["Acct-Delay-Time"] = pkt["Acct-Delay-Time"][0] + self.timeout
|
|
154
|
+
else:
|
|
155
|
+
pkt["Acct-Delay-Time"] = self.timeout
|
|
156
|
+
|
|
157
|
+
now = time.time()
|
|
158
|
+
waitto = now + self.timeout
|
|
159
|
+
|
|
160
|
+
if not self._socket:
|
|
161
|
+
raise RuntimeError("No socket present")
|
|
162
|
+
|
|
163
|
+
self._socket.sendto(pkt.RequestPacket(), (self.server, port))
|
|
164
|
+
|
|
165
|
+
while now < waitto:
|
|
166
|
+
ready = self._poll.poll((waitto - now) * 1000)
|
|
167
|
+
|
|
168
|
+
if ready:
|
|
169
|
+
rawreply = self._socket.recv(4096)
|
|
170
|
+
else:
|
|
171
|
+
now = time.time()
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
reply = pkt.CreateReply(packet=rawreply)
|
|
176
|
+
if pkt.VerifyReply(reply, rawreply):
|
|
177
|
+
return reply
|
|
178
|
+
except packet.PacketError:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
now = time.time()
|
|
182
|
+
|
|
183
|
+
raise Timeout
|
|
184
|
+
|
|
185
|
+
def SendPacket(self, pkt: packet.PacketImplementation): # type: ignore
|
|
186
|
+
"""Send a packet to a RADIUS server.
|
|
187
|
+
|
|
188
|
+
:param pkt: the packet to send
|
|
189
|
+
:type pkt: pyrad.packet.Packet
|
|
190
|
+
:return: the reply packet received
|
|
191
|
+
:rtype: pyrad.packet.Packet
|
|
192
|
+
:raise Timeout: RADIUS server does not reply
|
|
193
|
+
"""
|
|
194
|
+
if isinstance(pkt, packet.AuthPacket):
|
|
195
|
+
if pkt.auth_type == "eap-md5":
|
|
196
|
+
# Creating EAP-Identity
|
|
197
|
+
password = pkt[2][0] if 2 in pkt else pkt[1][0]
|
|
198
|
+
pkt[79] = [
|
|
199
|
+
struct.pack(
|
|
200
|
+
"!BBHB%ds" % len(password),
|
|
201
|
+
EAP_CODE_RESPONSE,
|
|
202
|
+
packet.CurrentID,
|
|
203
|
+
len(password) + 5,
|
|
204
|
+
EAP_TYPE_IDENTITY,
|
|
205
|
+
password,
|
|
206
|
+
)
|
|
207
|
+
]
|
|
208
|
+
reply = self._SendPacket(pkt, self.authport)
|
|
209
|
+
if (
|
|
210
|
+
reply
|
|
211
|
+
and reply.code == packet.AccessChallenge
|
|
212
|
+
and pkt.auth_type == "eap-md5"
|
|
213
|
+
):
|
|
214
|
+
# Got an Access-Challenge
|
|
215
|
+
eap_code, eap_id, eap_size, eap_type, eap_md5 = struct.unpack(
|
|
216
|
+
"!BBHB%ds" % (len(reply[79][0]) - 5), reply[79][0]
|
|
217
|
+
)
|
|
218
|
+
# Sending back an EAP-Type-MD5-Challenge
|
|
219
|
+
# Thank god for http://www.secdev.org/python/eapy.py
|
|
220
|
+
client_pw = pkt[2][0] if 2 in pkt else pkt[1][0]
|
|
221
|
+
md5_challenge = hashlib.md5(
|
|
222
|
+
struct.pack("!B", eap_id) + client_pw + eap_md5[1:]
|
|
223
|
+
).digest()
|
|
224
|
+
pkt[79] = [
|
|
225
|
+
struct.pack(
|
|
226
|
+
"!BBHBB",
|
|
227
|
+
2,
|
|
228
|
+
eap_id,
|
|
229
|
+
len(md5_challenge) + 6,
|
|
230
|
+
4,
|
|
231
|
+
len(md5_challenge),
|
|
232
|
+
)
|
|
233
|
+
+ md5_challenge
|
|
234
|
+
]
|
|
235
|
+
# Copy over Challenge-State
|
|
236
|
+
pkt[24] = reply[24]
|
|
237
|
+
reply = self._SendPacket(pkt, self.authport)
|
|
238
|
+
return reply
|
|
239
|
+
elif isinstance(pkt, packet.CoAPacket):
|
|
240
|
+
return self._SendPacket(pkt, self.coaport)
|
|
241
|
+
else:
|
|
242
|
+
return self._SendPacket(pkt, self.acctport)
|