pyrad 2.4__py3-none-any.whl → 2.5.0__py3-none-any.whl
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.
- docs/Makefile +20 -0
- docs/make.bat +36 -0
- docs/source/_static/logo.png +0 -0
- docs/source/api/client.rst +10 -0
- docs/source/api/dictionary.rst +10 -0
- docs/source/api/host.rst +7 -0
- docs/source/api/packet.rst +48 -0
- docs/source/api/proxy.rst +7 -0
- docs/source/api/server.rst +13 -0
- docs/source/conf.py +158 -0
- docs/source/index.rst +75 -0
- example/acct.py +41 -0
- example/auth.py +37 -0
- example/auth_async.py +164 -0
- example/client-coa.py +61 -0
- example/coa.py +40 -0
- example/dictionary +405 -0
- example/dictionary.freeradius +91 -0
- example/pyrad.log +0 -0
- example/server.py +68 -0
- example/server_async.py +117 -0
- example/status.py +26 -0
- pyrad/__init__.py +3 -3
- pyrad/client.py +14 -6
- pyrad/client_async.py +16 -13
- pyrad/dictfile.py +2 -5
- pyrad/dictionary.py +6 -7
- pyrad/host.py +1 -1
- pyrad/packet.py +145 -114
- pyrad/proxy.py +2 -2
- pyrad/server.py +3 -7
- pyrad/server_async.py +3 -4
- pyrad/tests/__init__.py +5 -0
- pyrad/tests/mock.py +145 -0
- pyrad/tests/test_bidict.py +56 -0
- pyrad/tests/test_client.py +183 -0
- pyrad/tests/test_dictionary.py +357 -0
- pyrad/tests/test_host.py +87 -0
- pyrad/tests/test_packet.py +679 -0
- pyrad/tests/test_proxy.py +96 -0
- pyrad/tests/test_server.py +323 -0
- pyrad/tests/test_tools.py +126 -0
- pyrad/tools.py +254 -158
- {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info}/METADATA +44 -20
- pyrad-2.5.0.dist-info/RECORD +51 -0
- {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info}/WHEEL +1 -1
- {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info/licenses}/LICENSE.txt +1 -1
- pyrad-2.5.0.dist-info/top_level.txt +3 -0
- pyrad-2.4.dist-info/RECORD +0 -19
- pyrad-2.4.dist-info/top_level.txt +0 -1
- {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info}/zip-safe +0 -0
pyrad/tools.py
CHANGED
|
@@ -1,236 +1,332 @@
|
|
|
1
1
|
# tools.py
|
|
2
2
|
#
|
|
3
3
|
# Utility functions
|
|
4
|
-
from netaddr import IPAddress
|
|
5
|
-
from netaddr import IPNetwork
|
|
6
|
-
import struct
|
|
7
|
-
import six
|
|
8
4
|
import binascii
|
|
5
|
+
import ipaddress
|
|
6
|
+
import struct
|
|
9
7
|
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if isinstance(str, six.text_type):
|
|
15
|
-
return str.encode('utf-8')
|
|
16
|
-
else:
|
|
17
|
-
return str
|
|
9
|
+
# -------------------------
|
|
10
|
+
# Encoding helpers
|
|
11
|
+
# -------------------------
|
|
18
12
|
|
|
13
|
+
def EncodeString(value):
|
|
14
|
+
"""
|
|
15
|
+
Encode a RADIUS 'string' value to bytes (UTF-8).
|
|
16
|
+
Accepts: str -> bytes, bytes -> bytes
|
|
17
|
+
"""
|
|
18
|
+
if value is None:
|
|
19
|
+
return b""
|
|
20
|
+
if isinstance(value, bytes):
|
|
21
|
+
if len(value) > 253:
|
|
22
|
+
raise ValueError("Can only encode strings of <= 253 characters")
|
|
23
|
+
return value
|
|
24
|
+
if isinstance(value, str):
|
|
25
|
+
if len(value) > 253:
|
|
26
|
+
raise ValueError("Can only encode strings of <= 253 characters")
|
|
27
|
+
return value.encode("utf-8")
|
|
28
|
+
raise TypeError("Can only encode str/bytes as string")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def EncodeOctets(value):
|
|
32
|
+
"""
|
|
33
|
+
Encodes RADIUS attributes of type "octets" into a byte sequence.
|
|
34
|
+
|
|
35
|
+
Supported inputs:
|
|
36
|
+
- bytes / bytearray:
|
|
37
|
+
* If the value starts with b"0x", it is treated as a hex string and decoded.
|
|
38
|
+
* Otherwise the byte value is passed through unchanged.
|
|
39
|
+
- str:
|
|
40
|
+
* "0x..." → hexadecimal representation, decoded into bytes
|
|
41
|
+
* Decimal string (e.g. "65"):
|
|
42
|
+
- 0..255 → encoded as a single byte
|
|
43
|
+
- >255 → encoded as a minimal big-endian byte sequence
|
|
44
|
+
* Any other string is UTF-8 encoded
|
|
45
|
+
|
|
46
|
+
Constraints:
|
|
47
|
+
- The resulting byte sequence must not exceed 253 bytes
|
|
48
|
+
(RADIUS attribute size limit).
|
|
49
|
+
|
|
50
|
+
This behavior preserves compatibility with legacy pyrad dictionary
|
|
51
|
+
definitions and existing test cases.
|
|
52
|
+
"""
|
|
53
|
+
if value is None:
|
|
54
|
+
return b""
|
|
55
|
+
|
|
56
|
+
if isinstance(value, (bytes, bytearray)):
|
|
57
|
+
b = bytes(value)
|
|
58
|
+
if b.startswith(b"0x"):
|
|
59
|
+
out = binascii.unhexlify(b[2:])
|
|
60
|
+
else:
|
|
61
|
+
out = b
|
|
62
|
+
|
|
63
|
+
if len(out) > 253:
|
|
64
|
+
raise ValueError("Can only encode strings of <= 253 characters")
|
|
65
|
+
return out
|
|
66
|
+
|
|
67
|
+
if isinstance(value, str):
|
|
68
|
+
s = value
|
|
69
|
+
if s.startswith("0x"):
|
|
70
|
+
out = binascii.unhexlify(s[2:])
|
|
71
|
+
elif s.isdecimal():
|
|
72
|
+
n = int(s)
|
|
73
|
+
if n < 0:
|
|
74
|
+
raise ValueError("Octet decimal value must be >= 0")
|
|
75
|
+
if n <= 255:
|
|
76
|
+
out = struct.pack("!B", n)
|
|
77
|
+
else:
|
|
78
|
+
byte_len = (n.bit_length() + 7) // 8
|
|
79
|
+
out = n.to_bytes(byte_len, "big")
|
|
80
|
+
else:
|
|
81
|
+
out = s.encode("utf-8")
|
|
82
|
+
|
|
83
|
+
if len(out) > 253:
|
|
84
|
+
raise ValueError("Can only encode strings of <= 253 characters")
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
raise TypeError("Can only encode str/bytes as octets")
|
|
19
88
|
|
|
20
|
-
def EncodeOctets(str):
|
|
21
|
-
if len(str) > 253:
|
|
22
|
-
raise ValueError('Can only encode strings of <= 253 characters')
|
|
23
89
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
90
|
+
def EncodeAddress(addr):
|
|
91
|
+
"""
|
|
92
|
+
Encode a RADIUS 'ipaddr' value.
|
|
93
|
+
Traditionally IPv4, but accept IPv6 as well (robust for real-world use).
|
|
94
|
+
"""
|
|
95
|
+
if not isinstance(addr, str):
|
|
96
|
+
raise TypeError("Address has to be a string")
|
|
97
|
+
return ipaddress.ip_address(addr).packed
|
|
29
98
|
|
|
30
99
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
100
|
+
def EncodeIPv6Address(addr):
|
|
101
|
+
"""
|
|
102
|
+
Encode a RADIUS 'ipv6addr' value to 16 bytes.
|
|
103
|
+
Accepts: str, IPv6Address
|
|
104
|
+
"""
|
|
105
|
+
if isinstance(addr, ipaddress.IPv6Address):
|
|
106
|
+
return addr.packed
|
|
107
|
+
if not isinstance(addr, str):
|
|
108
|
+
raise TypeError("IPv6 Address has to be a string")
|
|
109
|
+
return ipaddress.IPv6Address(addr).packed
|
|
35
110
|
|
|
36
111
|
|
|
37
|
-
def EncodeIPv6Prefix(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
112
|
+
def EncodeIPv6Prefix(value, default_prefixlen=128):
|
|
113
|
+
"""
|
|
114
|
+
Encode a RADIUS 'ipv6prefix' value.
|
|
115
|
+
|
|
116
|
+
Accepts:
|
|
117
|
+
- "2001:db8::/64" (str)
|
|
118
|
+
- "2001:db8::" (str) -> uses default_prefixlen
|
|
119
|
+
- ipaddress.IPv6Network
|
|
120
|
+
- ipaddress.IPv6Address -> uses default_prefixlen
|
|
121
|
+
- netaddr.IPNetwork (duck-typed via .ip/.prefixlen)
|
|
122
|
+
"""
|
|
123
|
+
# 1) string input
|
|
124
|
+
if isinstance(value, str):
|
|
125
|
+
if "/" in value:
|
|
126
|
+
net = ipaddress.ip_network(value, strict=False)
|
|
127
|
+
else:
|
|
128
|
+
addr = ipaddress.IPv6Address(value)
|
|
129
|
+
net = ipaddress.IPv6Network((addr, default_prefixlen), strict=False)
|
|
130
|
+
|
|
131
|
+
# 2) stdlib ipaddress objects
|
|
132
|
+
elif isinstance(value, ipaddress.IPv6Network):
|
|
133
|
+
net = value
|
|
134
|
+
elif isinstance(value, ipaddress.IPv6Address):
|
|
135
|
+
net = ipaddress.IPv6Network((value, default_prefixlen), strict=False)
|
|
136
|
+
|
|
137
|
+
# 3) netaddr fallback (duck typing)
|
|
138
|
+
elif hasattr(value, "ip") and hasattr(value, "prefixlen"):
|
|
139
|
+
# netaddr.IPNetwork uses .ip and .prefixlen
|
|
140
|
+
return struct.pack("2B", 0, int(value.prefixlen)) + value.ip.packed
|
|
42
141
|
|
|
142
|
+
else:
|
|
143
|
+
raise TypeError("IPv6 Prefix has to be a string, IPv6Network, IPv6Address, or netaddr IPNetwork")
|
|
43
144
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return
|
|
145
|
+
if getattr(net, "version", None) != 6:
|
|
146
|
+
raise ValueError("not an IPv6 prefix")
|
|
147
|
+
|
|
148
|
+
return struct.pack("2B", 0, net.prefixlen) + net.network_address.packed
|
|
48
149
|
|
|
49
150
|
|
|
50
|
-
def EncodeAscendBinary(
|
|
151
|
+
def EncodeAscendBinary(orig_str):
|
|
51
152
|
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Example: 'family=ipv4 action=discard direction=in dst=10.10.255.254/32'
|
|
55
|
-
|
|
56
|
-
Type:
|
|
57
|
-
family ipv4(default) or ipv6
|
|
58
|
-
action discard(default) or accept
|
|
59
|
-
direction in(default) or out
|
|
60
|
-
src source prefix (default ignore)
|
|
61
|
-
dst destination prefix (default ignore)
|
|
62
|
-
proto protocol number / next-header number (default ignore)
|
|
63
|
-
sport source port (default ignore)
|
|
64
|
-
dport destination port (default ignore)
|
|
65
|
-
sportq source port qualifier (default 0)
|
|
66
|
-
dportq destination port qualifier (default 0)
|
|
67
|
-
|
|
68
|
-
Source/Destination Port Qualifier:
|
|
69
|
-
0 no compare
|
|
70
|
-
1 less than
|
|
71
|
-
2 equal to
|
|
72
|
-
3 greater than
|
|
73
|
-
4 not equal to
|
|
153
|
+
Ascend binary format encoder.
|
|
74
154
|
"""
|
|
75
|
-
|
|
76
155
|
terms = {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
156
|
+
"family": b"\x01",
|
|
157
|
+
"action": b"\x00",
|
|
158
|
+
"direction": b"\x01",
|
|
159
|
+
"src": b"\x00\x00\x00\x00",
|
|
160
|
+
"dst": b"\x00\x00\x00\x00",
|
|
161
|
+
"srcl": b"\x00",
|
|
162
|
+
"dstl": b"\x00",
|
|
163
|
+
"proto": b"\x00",
|
|
164
|
+
"sport": b"\x00\x00",
|
|
165
|
+
"dport": b"\x00\x00",
|
|
166
|
+
"sportq": b"\x00",
|
|
167
|
+
"dportq": b"\x00",
|
|
89
168
|
}
|
|
90
169
|
|
|
91
|
-
for t in
|
|
92
|
-
key, value = t.split(
|
|
93
|
-
if key ==
|
|
94
|
-
terms[key] = b
|
|
95
|
-
if terms[
|
|
96
|
-
terms[
|
|
97
|
-
if terms[
|
|
98
|
-
terms[
|
|
99
|
-
elif key ==
|
|
100
|
-
terms[key] = b
|
|
101
|
-
elif key ==
|
|
102
|
-
terms[key] = b
|
|
103
|
-
elif key ==
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
terms[key] = struct.pack(
|
|
109
|
-
elif key
|
|
110
|
-
terms[key] = struct.pack(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
terms[
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
170
|
+
for t in orig_str.split(" "):
|
|
171
|
+
key, value = t.split("=")
|
|
172
|
+
if key == "family" and value == "ipv6":
|
|
173
|
+
terms[key] = b"\x03"
|
|
174
|
+
if terms["src"] == b"\x00\x00\x00\x00":
|
|
175
|
+
terms["src"] = 16 * b"\x00"
|
|
176
|
+
if terms["dst"] == b"\x00\x00\x00\x00":
|
|
177
|
+
terms["dst"] = 16 * b"\x00"
|
|
178
|
+
elif key == "action" and value == "accept":
|
|
179
|
+
terms[key] = b"\x01"
|
|
180
|
+
elif key == "action" and value == "redirect":
|
|
181
|
+
terms[key] = b"\x20"
|
|
182
|
+
elif key == "direction" and value == "out":
|
|
183
|
+
terms[key] = b"\x00"
|
|
184
|
+
elif key in ("src", "dst"):
|
|
185
|
+
net = ipaddress.ip_network(value, strict=False)
|
|
186
|
+
terms[key] = net.network_address.packed
|
|
187
|
+
terms[key + "l"] = struct.pack("B", net.prefixlen)
|
|
188
|
+
elif key in ("sport", "dport"):
|
|
189
|
+
terms[key] = struct.pack("!H", int(value))
|
|
190
|
+
elif key in ("sportq", "dportq", "proto"):
|
|
191
|
+
terms[key] = struct.pack("B", int(value))
|
|
192
|
+
|
|
193
|
+
trailer = 8 * b"\x00"
|
|
194
|
+
return b"".join((
|
|
195
|
+
terms["family"], terms["action"], terms["direction"], b"\x00",
|
|
196
|
+
terms["src"], terms["dst"], terms["srcl"], terms["dstl"],
|
|
197
|
+
terms["proto"], b"\x00", terms["sport"], terms["dport"],
|
|
198
|
+
terms["sportq"], terms["dportq"], b"\x00\x00", trailer
|
|
199
|
+
))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def EncodeInteger(num, format="!I"):
|
|
121
203
|
try:
|
|
122
204
|
num = int(num)
|
|
123
|
-
except:
|
|
124
|
-
raise TypeError(
|
|
205
|
+
except Exception:
|
|
206
|
+
raise TypeError("Can not encode non-integer as integer")
|
|
125
207
|
return struct.pack(format, num)
|
|
126
208
|
|
|
127
|
-
|
|
209
|
+
|
|
210
|
+
def EncodeInteger64(num, format="!Q"):
|
|
128
211
|
try:
|
|
129
212
|
num = int(num)
|
|
130
|
-
except:
|
|
131
|
-
raise TypeError(
|
|
213
|
+
except Exception:
|
|
214
|
+
raise TypeError("Can not encode non-integer as integer64")
|
|
132
215
|
return struct.pack(format, num)
|
|
133
216
|
|
|
217
|
+
|
|
134
218
|
def EncodeDate(num):
|
|
135
219
|
if not isinstance(num, int):
|
|
136
|
-
raise TypeError(
|
|
137
|
-
return struct.pack(
|
|
220
|
+
raise TypeError("Can not encode non-integer as date")
|
|
221
|
+
return struct.pack("!I", num)
|
|
138
222
|
|
|
139
223
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
224
|
+
# -------------------------
|
|
225
|
+
# Decoding helpers
|
|
226
|
+
# -------------------------
|
|
227
|
+
|
|
228
|
+
def DecodeString(value):
|
|
229
|
+
# Be tolerant: bytes -> utf-8 (replace), else passthrough
|
|
230
|
+
if isinstance(value, bytes):
|
|
231
|
+
return value.decode("utf-8", errors="replace")
|
|
232
|
+
return value
|
|
145
233
|
|
|
146
234
|
|
|
147
|
-
def DecodeOctets(
|
|
148
|
-
return
|
|
235
|
+
def DecodeOctets(value):
|
|
236
|
+
return value
|
|
149
237
|
|
|
150
238
|
|
|
151
239
|
def DecodeAddress(addr):
|
|
152
|
-
return
|
|
240
|
+
return str(ipaddress.ip_address(addr))
|
|
153
241
|
|
|
154
242
|
|
|
155
243
|
def DecodeIPv6Prefix(addr):
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
244
|
+
# RADIUS IPv6-Prefix is: 2 bytes (reserved, prefixlen) + prefix bytes (0..16)
|
|
245
|
+
addr = addr + b"\x00" * (18 - len(addr))
|
|
246
|
+
_, length = struct.unpack("!BB", addr[:2])
|
|
247
|
+
prefix_bytes = addr[2:18]
|
|
248
|
+
prefix = ipaddress.IPv6Address(prefix_bytes)
|
|
249
|
+
return str(ipaddress.IPv6Network((prefix, int(length)), strict=False))
|
|
159
250
|
|
|
160
251
|
|
|
161
252
|
def DecodeIPv6Address(addr):
|
|
162
|
-
addr = addr + b
|
|
163
|
-
|
|
164
|
-
return str(IPAddress(prefix))
|
|
253
|
+
addr = addr + b"\x00" * (16 - len(addr))
|
|
254
|
+
return str(ipaddress.IPv6Address(addr))
|
|
165
255
|
|
|
166
256
|
|
|
167
|
-
def DecodeAscendBinary(
|
|
168
|
-
return
|
|
257
|
+
def DecodeAscendBinary(value):
|
|
258
|
+
return value
|
|
169
259
|
|
|
170
260
|
|
|
171
|
-
def DecodeInteger(num, format=
|
|
172
|
-
return
|
|
261
|
+
def DecodeInteger(num, format="!I"):
|
|
262
|
+
return struct.unpack(format, num)[0]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def DecodeInteger64(num, format="!Q"):
|
|
266
|
+
return struct.unpack(format, num)[0]
|
|
173
267
|
|
|
174
|
-
def DecodeInteger64(num, format='!Q'):
|
|
175
|
-
return (struct.unpack(format, num))[0]
|
|
176
268
|
|
|
177
269
|
def DecodeDate(num):
|
|
178
|
-
return
|
|
270
|
+
return struct.unpack("!I", num)[0]
|
|
271
|
+
|
|
179
272
|
|
|
273
|
+
# -------------------------
|
|
274
|
+
# Attribute encode/decode dispatch
|
|
275
|
+
# -------------------------
|
|
180
276
|
|
|
181
277
|
def EncodeAttr(datatype, value):
|
|
182
|
-
if datatype ==
|
|
278
|
+
if datatype == "string":
|
|
183
279
|
return EncodeString(value)
|
|
184
|
-
elif datatype ==
|
|
280
|
+
elif datatype == "octets":
|
|
185
281
|
return EncodeOctets(value)
|
|
186
|
-
elif datatype ==
|
|
282
|
+
elif datatype == "integer":
|
|
187
283
|
return EncodeInteger(value)
|
|
188
|
-
elif datatype ==
|
|
284
|
+
elif datatype == "ipaddr":
|
|
189
285
|
return EncodeAddress(value)
|
|
190
|
-
elif datatype ==
|
|
286
|
+
elif datatype == "ipv6prefix":
|
|
191
287
|
return EncodeIPv6Prefix(value)
|
|
192
|
-
elif datatype ==
|
|
288
|
+
elif datatype == "ipv6addr":
|
|
193
289
|
return EncodeIPv6Address(value)
|
|
194
|
-
elif datatype ==
|
|
290
|
+
elif datatype == "abinary":
|
|
195
291
|
return EncodeAscendBinary(value)
|
|
196
|
-
elif datatype ==
|
|
197
|
-
return EncodeInteger(value,
|
|
198
|
-
elif datatype ==
|
|
199
|
-
return EncodeInteger(value,
|
|
200
|
-
elif datatype ==
|
|
201
|
-
return EncodeInteger(value,
|
|
202
|
-
elif datatype ==
|
|
292
|
+
elif datatype == "signed":
|
|
293
|
+
return EncodeInteger(value, "!i")
|
|
294
|
+
elif datatype == "short":
|
|
295
|
+
return EncodeInteger(value, "!H")
|
|
296
|
+
elif datatype == "byte":
|
|
297
|
+
return EncodeInteger(value, "!B")
|
|
298
|
+
elif datatype == "date":
|
|
203
299
|
return EncodeDate(value)
|
|
204
|
-
elif datatype ==
|
|
300
|
+
elif datatype == "integer64":
|
|
205
301
|
return EncodeInteger64(value)
|
|
206
302
|
else:
|
|
207
|
-
raise ValueError(
|
|
303
|
+
raise ValueError("Unknown attribute type %s" % datatype)
|
|
208
304
|
|
|
209
305
|
|
|
210
306
|
def DecodeAttr(datatype, value):
|
|
211
|
-
if datatype ==
|
|
307
|
+
if datatype == "string":
|
|
212
308
|
return DecodeString(value)
|
|
213
|
-
elif datatype ==
|
|
309
|
+
elif datatype == "octets":
|
|
214
310
|
return DecodeOctets(value)
|
|
215
|
-
elif datatype ==
|
|
311
|
+
elif datatype == "integer":
|
|
216
312
|
return DecodeInteger(value)
|
|
217
|
-
elif datatype ==
|
|
313
|
+
elif datatype == "ipaddr":
|
|
218
314
|
return DecodeAddress(value)
|
|
219
|
-
elif datatype ==
|
|
315
|
+
elif datatype == "ipv6prefix":
|
|
220
316
|
return DecodeIPv6Prefix(value)
|
|
221
|
-
elif datatype ==
|
|
317
|
+
elif datatype == "ipv6addr":
|
|
222
318
|
return DecodeIPv6Address(value)
|
|
223
|
-
elif datatype ==
|
|
319
|
+
elif datatype == "abinary":
|
|
224
320
|
return DecodeAscendBinary(value)
|
|
225
|
-
elif datatype ==
|
|
226
|
-
return DecodeInteger(value,
|
|
227
|
-
elif datatype ==
|
|
228
|
-
return DecodeInteger(value,
|
|
229
|
-
elif datatype ==
|
|
230
|
-
return DecodeInteger(value,
|
|
231
|
-
elif datatype ==
|
|
321
|
+
elif datatype == "signed":
|
|
322
|
+
return DecodeInteger(value, "!i")
|
|
323
|
+
elif datatype == "short":
|
|
324
|
+
return DecodeInteger(value, "!H")
|
|
325
|
+
elif datatype == "byte":
|
|
326
|
+
return DecodeInteger(value, "!B")
|
|
327
|
+
elif datatype == "date":
|
|
232
328
|
return DecodeDate(value)
|
|
233
|
-
elif datatype ==
|
|
329
|
+
elif datatype == "integer64":
|
|
234
330
|
return DecodeInteger64(value)
|
|
235
331
|
else:
|
|
236
|
-
raise ValueError(
|
|
332
|
+
raise ValueError("Unknown attribute type %s" % datatype)
|
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyrad
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: RADIUS tools
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Keywords: radius,authentication
|
|
10
|
-
Platform: UNKNOWN
|
|
5
|
+
Author-email: Istvan Ruzman <istvan@ruzman.eu>, Christian Giese <gic@gicnet.de>, Stefan Lieberth <stefan@lieberth.net>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Repository, https://github.com/pyradius/pyrad
|
|
8
|
+
Project-URL: Documentation, https://pyradius-pyrad.readthedocs.io
|
|
9
|
+
Keywords: radius,authentication,AAA,accounting,authorization,RADIUS
|
|
11
10
|
Classifier: Development Status :: 6 - Mature
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
13
12
|
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
-
Classifier: Programming Language :: Python ::
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
14
|
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
22
|
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
|
|
20
|
-
Requires-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/x-rst
|
|
25
|
+
License-File: LICENSE.txt
|
|
26
|
+
Requires-Dist: netaddr>=0.8.0
|
|
27
|
+
Requires-Dist: six>=1.16.0
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
30
|
+
Requires-Dist: mock; python_version < "3.10" and extra == "test"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
.. image:: https://github.com/pyradius/pyrad/actions/workflows/python-test.yml/badge.svg?branch=master
|
|
35
|
+
:target: https://github.com/pyradius/pyrad/actions/workflows/python-test.yml
|
|
25
36
|
.. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
|
|
26
37
|
:target: https://coveralls.io/github/pyradius/pyrad?branch=master
|
|
27
38
|
.. image:: https://img.shields.io/pypi/v/pyrad.svg
|
|
@@ -30,11 +41,13 @@ Requires-Dist: netaddr
|
|
|
30
41
|
:target: https://pypi.python.org/pypi/pyrad
|
|
31
42
|
.. image:: https://img.shields.io/pypi/dm/pyrad.svg
|
|
32
43
|
:target: https://pypi.python.org/pypi/pyrad
|
|
33
|
-
.. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
|
|
34
|
-
:target:
|
|
44
|
+
.. image:: https://readthedocs.org/projects/pyradius-pyrad/badge/?version=latest
|
|
45
|
+
:target: https://pyradius-pyrad.readthedocs.io/en/latest/?badge=latest
|
|
35
46
|
:alt: Documentation Status
|
|
36
47
|
.. image:: https://img.shields.io/pypi/l/pyrad.svg
|
|
37
48
|
:target: https://pypi.python.org/pypi/pyrad
|
|
49
|
+
.. image:: https://img.shields.io/badge/Chat-darkgreen
|
|
50
|
+
:target: https://matrix.to/#/#pyradius:matrix.org
|
|
38
51
|
|
|
39
52
|
Introduction
|
|
40
53
|
============
|
|
@@ -45,7 +58,6 @@ them and decoding responses.
|
|
|
45
58
|
|
|
46
59
|
Here is an example of doing a authentication request::
|
|
47
60
|
|
|
48
|
-
from __future__ import print_function
|
|
49
61
|
from pyrad.client import Client
|
|
50
62
|
from pyrad.dictionary import Dictionary
|
|
51
63
|
import pyrad.packet
|
|
@@ -75,7 +87,7 @@ Here is an example of doing a authentication request::
|
|
|
75
87
|
Requirements & Installation
|
|
76
88
|
===========================
|
|
77
89
|
|
|
78
|
-
pyrad requires Python
|
|
90
|
+
pyrad requires Python 3.8 or later
|
|
79
91
|
|
|
80
92
|
Installing is simple; pyrad uses the standard distutils system for installing
|
|
81
93
|
Python modules::
|
|
@@ -89,6 +101,10 @@ Author, Copyright, Availability
|
|
|
89
101
|
pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by
|
|
90
102
|
Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
|
|
91
103
|
|
|
104
|
+
We’re looking for contributors to support the pyrad team! If you’re interested in
|
|
105
|
+
helping with development, testing, documentation, or other areas, please contact
|
|
106
|
+
us directly.
|
|
107
|
+
|
|
92
108
|
This project is licensed under a BSD license.
|
|
93
109
|
|
|
94
110
|
Copyright and license information can be found in the LICENSE.txt file.
|
|
@@ -99,4 +115,12 @@ https://pypi.org/project/pyrad/
|
|
|
99
115
|
Bugs and wishes can be submitted in the pyrad issue tracker on github:
|
|
100
116
|
https://github.com/pyradius/pyrad/issues
|
|
101
117
|
|
|
118
|
+
Related Projects & Forks
|
|
119
|
+
========================
|
|
120
|
+
|
|
121
|
+
**pyrad2:** Noteworthy fork with experimental RadSec (RFC 6614) support. Targets Python 3.12+,
|
|
122
|
+
adds extensive type hints, boosts test coverage, and includes fresh bug fixes.
|
|
123
|
+
https://github.com/nicholasamorim/pyrad2
|
|
102
124
|
|
|
125
|
+
**pyrad-server:** Lab-grade RADIUS test server built on top of pyrad.
|
|
126
|
+
https://github.com/slieberth/pyrad-server
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
docs/Makefile,sha256=De9yQi78Rqt6niypd7CgZSkhyHFEx5cXhh6ZL3YT1Yc,606
|
|
2
|
+
docs/make.bat,sha256=MIkBA-XhK2GzLus-jttyGfmQMlVYtQT23DOTPKnICw8,813
|
|
3
|
+
docs/source/conf.py,sha256=BmMQb6oxGNAzfGGDT6WZ4MYrd_h0kf5-00Iasu2F-Uk,5137
|
|
4
|
+
docs/source/index.rst,sha256=i_Fb3-3lEDGp8iMYdgQphqexeoiGY0WREO-btdlnelA,1650
|
|
5
|
+
docs/source/_static/logo.png,sha256=xr7ETIziytN2Sl9WkOCnAKWwikXJYwvpIB7stu5a6iE,1247
|
|
6
|
+
docs/source/api/client.rst,sha256=Bg2tB-hlaRtiZleaJXEOrA0weDi0w25lS0xzw_k0xKE,181
|
|
7
|
+
docs/source/api/dictionary.rst,sha256=-dQM3gTaZjcEVMe0RlDZ5TgtBhnk8A927TlrZqvjFGQ,210
|
|
8
|
+
docs/source/api/host.rst,sha256=R1IUSCWpwfW7xha-Z18xyaq3kDhlsmxxX9vnJx1fPsw,153
|
|
9
|
+
docs/source/api/packet.rst,sha256=anx-kgMZN27cp0oMhQIgnts5HvUFInK-UsvCDRD0L40,1013
|
|
10
|
+
docs/source/api/proxy.rst,sha256=pgdVrBb1vMqFrjV3Z27WVl3LReIzbPDoe_SVXHMZctY,135
|
|
11
|
+
docs/source/api/server.rst,sha256=R8e3frO_Dc7Jw1EmB5BthTLS_xnRfA-nsCSHccEG44g,234
|
|
12
|
+
example/acct.py,sha256=X9O3YKdR-pEEoziyfJR9b1Dhh8kfDsrwm-WUji4PlT4,1239
|
|
13
|
+
example/auth.py,sha256=GubmBRDEP955YoA-vK5He-35B266-sOKx0kOfhlBU98,1051
|
|
14
|
+
example/auth_async.py,sha256=J18gwQHscjeVhQT0DVjTJQTZ546REypnsM98vzul_C8,4683
|
|
15
|
+
example/client-coa.py,sha256=OR2vwLvGegPUaeHCYMjo59xBi-SIQ8MWwBKZH2EqPKE,1811
|
|
16
|
+
example/coa.py,sha256=avdyiA4zdBZDqzGAfvlJ5_f8HWy9ggKp0llnwj5u63g,954
|
|
17
|
+
example/dictionary,sha256=KnfpaTXwdFGAxgoVoNDMWGGj0teZ8xJCLOWo2bXpIG4,13085
|
|
18
|
+
example/dictionary.freeradius,sha256=Sfo8911YmOex3vNyF42opvD4C2C-NoK3Az6wRobmGXU,3851
|
|
19
|
+
example/pyrad.log,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
example/server.py,sha256=kvXl_7HpW2xXy5U-LU-9VRSVJubeHll6XSnbms2M0rs,1993
|
|
21
|
+
example/server_async.py,sha256=va4bEKX796ePDrM_pAERAXNaSUsxrelLoa3fzp2n4TY,3442
|
|
22
|
+
example/status.py,sha256=jyUgdW_sUdUgjyChnKEQJQf-bnLr6KXC3479mi5-q9Q,724
|
|
23
|
+
pyrad/__init__.py,sha256=UHLdGJEv8IRKt2TsjbJsxYO3BB-grtobpuNp0iXftGo,1537
|
|
24
|
+
pyrad/bidict.py,sha256=wgSr6odl-sYqkHrAFvCqRPjKOPpck4tZ-YxXivmPLsQ,870
|
|
25
|
+
pyrad/client.py,sha256=scOSXMs1Oee7m7w5xdwHnCoxpeBD298tgKDgxsSRhj4,8498
|
|
26
|
+
pyrad/client_async.py,sha256=XIi9tEo3sfmmbG33xs3e9YDa6GGIiAMDC4LqS0VFNno,14758
|
|
27
|
+
pyrad/curved.py,sha256=PbtHrJjk9S5BBSF9jmDvOMZKkaJcbAaICodTQpqDbBg,2204
|
|
28
|
+
pyrad/dictfile.py,sha256=ymcfT95v28vnsytcnP4LnsBje9RBw36mtxUMAZOwBQs,2858
|
|
29
|
+
pyrad/dictionary.py,sha256=KjWQTigR6HqOYN3U1hCK5I02LHqyxLNH35p9KIDXZQw,14375
|
|
30
|
+
pyrad/host.py,sha256=7e8d4mFvNTapNXmxyh62b7ddCLDFjc2hEc-5MsDUMe4,3628
|
|
31
|
+
pyrad/packet.py,sha256=QW22xekqdLvaoC4DwiEy0DLLZyOO-od7BEsZRLx9ykI,34546
|
|
32
|
+
pyrad/proxy.py,sha256=T3vBt8q3T4qbCO8JEXa90DOojbH9vC-YOID8kIJ-HO8,2510
|
|
33
|
+
pyrad/server.py,sha256=GriMqeMk0l16Nd3-yWzbE2eqkYD8X_KE7zOXiK6pvqs,12602
|
|
34
|
+
pyrad/server_async.py,sha256=mQ--rt1se-pQVWLgoaXxyzds6AdzRV8LNax5Wvn4mNw,11889
|
|
35
|
+
pyrad/tools.py,sha256=3AN3YUqWmtMf_k8glIVMu2LI7rfriRV_06-1yQhFf0s,10094
|
|
36
|
+
pyrad/tests/__init__.py,sha256=E-zlu3gcB6Pm97MjuIlChVLyKBqHcP8V9aKsRU1NG5I,104
|
|
37
|
+
pyrad/tests/mock.py,sha256=VJUVUgfpiPI5fCc9ILg_ZLeSPpMZjSnwD6C7NG39QJw,3349
|
|
38
|
+
pyrad/tests/test_bidict.py,sha256=eAeOcxs3AoCOzE5M-zRuPIb2p8YBGkDzmGXtQ9IxodU,2257
|
|
39
|
+
pyrad/tests/test_client.py,sha256=PK4-rvfL-EcSecIM9zKjQLViKpXjshSGIkgYVG9HjeA,6724
|
|
40
|
+
pyrad/tests/test_dictionary.py,sha256=S3LzstjbmyqLYZgHTmxwxCRQzefV0XLs_cWP5AwU6zg,13618
|
|
41
|
+
pyrad/tests/test_host.py,sha256=IOqxVXNLB8znQQkpxvRMYSja_oo2lVwQ_IRjfS_IIZw,2597
|
|
42
|
+
pyrad/tests/test_packet.py,sha256=7d7_NObiEZeiGArz9uVXY8qZkzv_F8z8Saz41TlbK0s,27085
|
|
43
|
+
pyrad/tests/test_proxy.py,sha256=JVhVCDqa4waixtNw82kMsDzVeiKgIeUU5fLf8mcp7E8,2981
|
|
44
|
+
pyrad/tests/test_server.py,sha256=UZo5RCyYJcYvuKvFlE0e0tWs4hjnSVVbJTqJ9YciVgA,11383
|
|
45
|
+
pyrad/tests/test_tools.py,sha256=PKc5tPzyartVCE4OIMzgkIIdeSIK3luQFPlrO3ivVFM,6617
|
|
46
|
+
pyrad-2.5.0.dist-info/licenses/LICENSE.txt,sha256=JXj4emdUhaoO3l80a9HFY7c61ekO4yLbInhCcET9r_U,1660
|
|
47
|
+
pyrad-2.5.0.dist-info/METADATA,sha256=FP3FxyfhjQeD8c3DkRhNnKP4Y8AhiQ--Yv7nFk7U-4c,4748
|
|
48
|
+
pyrad-2.5.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
49
|
+
pyrad-2.5.0.dist-info/top_level.txt,sha256=DfCXLna0BEU1ycerXhff_P0tIEOQr8_XsWCw-re1Xg8,19
|
|
50
|
+
pyrad-2.5.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
51
|
+
pyrad-2.5.0.dist-info/RECORD,,
|