pyrad 2.4__py3-none-any.whl → 2.5.1__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.1.dist-info}/METADATA +45 -22
- pyrad-2.5.1.dist-info/RECORD +51 -0
- {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info}/WHEEL +1 -1
- {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info/licenses}/LICENSE.txt +1 -1
- pyrad-2.5.1.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.1.dist-info}/zip-safe +0 -0
pyrad/packet.py
CHANGED
|
@@ -5,18 +5,26 @@
|
|
|
5
5
|
# A RADIUS packet as defined in RFC 2138
|
|
6
6
|
|
|
7
7
|
from collections import OrderedDict
|
|
8
|
+
from pyrad import tools
|
|
9
|
+
import hmac
|
|
8
10
|
import struct
|
|
11
|
+
import sys
|
|
12
|
+
|
|
9
13
|
try:
|
|
10
14
|
import secrets
|
|
11
15
|
random_generator = secrets.SystemRandom()
|
|
12
16
|
except ImportError:
|
|
13
17
|
import random
|
|
14
18
|
random_generator = random.SystemRandom()
|
|
15
|
-
import hmac
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
def _hmac_md5(*args, **kwargs):
|
|
22
|
+
"""Py3 hmac.new() wrapper with explicit MD5 digestmod."""
|
|
23
|
+
return hmac.new(*args, digestmod='MD5', **kwargs)
|
|
24
|
+
|
|
25
|
+
|
|
18
26
|
if sys.version_info >= (3, 0):
|
|
19
|
-
hmac_new =
|
|
27
|
+
hmac_new = _hmac_md5
|
|
20
28
|
else:
|
|
21
29
|
hmac_new = hmac.new
|
|
22
30
|
|
|
@@ -27,8 +35,6 @@ except ImportError:
|
|
|
27
35
|
# BBB for python 2.4
|
|
28
36
|
import md5
|
|
29
37
|
md5_constructor = md5.new
|
|
30
|
-
import six
|
|
31
|
-
from pyrad import tools
|
|
32
38
|
|
|
33
39
|
# Packet codes
|
|
34
40
|
AccessRequest = 1
|
|
@@ -60,7 +66,7 @@ class Packet(OrderedDict):
|
|
|
60
66
|
attributes the value will always be a sequence. pyrad makes sure
|
|
61
67
|
to preserve the ordering when encoding and decoding packets.
|
|
62
68
|
|
|
63
|
-
There are two ways to use the map
|
|
69
|
+
There are two ways to use the map interface: if attribute
|
|
64
70
|
names are used pyrad take care of en-/decoding data. If
|
|
65
71
|
the attribute type number (or a vendor ID/attribute type
|
|
66
72
|
tuple for vendor attributes) is used you work with the
|
|
@@ -70,7 +76,7 @@ class Packet(OrderedDict):
|
|
|
70
76
|
:obj:`AuthPacket` or :obj:`AcctPacket` classes.
|
|
71
77
|
"""
|
|
72
78
|
|
|
73
|
-
def __init__(self, code=0, id=None, secret=
|
|
79
|
+
def __init__(self, code=0, id=None, secret=b'', authenticator=None,
|
|
74
80
|
**attributes):
|
|
75
81
|
"""Constructor
|
|
76
82
|
|
|
@@ -91,13 +97,14 @@ class Packet(OrderedDict):
|
|
|
91
97
|
self.id = id
|
|
92
98
|
else:
|
|
93
99
|
self.id = CreateID()
|
|
94
|
-
if not isinstance(secret,
|
|
100
|
+
if not isinstance(secret, bytes):
|
|
95
101
|
raise TypeError('secret must be a binary string')
|
|
96
102
|
self.secret = secret
|
|
97
103
|
if authenticator is not None and \
|
|
98
|
-
not isinstance(authenticator,
|
|
104
|
+
not isinstance(authenticator, bytes):
|
|
99
105
|
raise TypeError('authenticator must be a binary string')
|
|
100
106
|
self.authenticator = authenticator
|
|
107
|
+
self.request_authenticator = None # used for storing request authenticator in reply packets
|
|
101
108
|
self.message_authenticator = None
|
|
102
109
|
self.raw_packet = None
|
|
103
110
|
|
|
@@ -124,7 +131,7 @@ class Packet(OrderedDict):
|
|
|
124
131
|
|
|
125
132
|
self.message_authenticator = True
|
|
126
133
|
# Maintain a zero octets content for md5 and hmac calculation.
|
|
127
|
-
self['Message-Authenticator'] = 16 *
|
|
134
|
+
self['Message-Authenticator'] = 16 * b'\00'
|
|
128
135
|
|
|
129
136
|
if self.id is None:
|
|
130
137
|
self.id = self.CreateID()
|
|
@@ -141,7 +148,7 @@ class Packet(OrderedDict):
|
|
|
141
148
|
hmac_constructor = hmac_new(self.secret)
|
|
142
149
|
|
|
143
150
|
# Maintain a zero octets content for md5 and hmac calculation.
|
|
144
|
-
self['Message-Authenticator'] = 16 *
|
|
151
|
+
self['Message-Authenticator'] = 16 * b'\00'
|
|
145
152
|
attr = self._PktEncodeAttributes()
|
|
146
153
|
|
|
147
154
|
header = struct.pack('!BBH', self.code, self.id,
|
|
@@ -150,7 +157,7 @@ class Packet(OrderedDict):
|
|
|
150
157
|
hmac_constructor.update(header[0:4])
|
|
151
158
|
if self.code in (AccountingRequest, DisconnectRequest,
|
|
152
159
|
CoARequest, AccountingResponse):
|
|
153
|
-
hmac_constructor.update(16 *
|
|
160
|
+
hmac_constructor.update(16 * b'\00')
|
|
154
161
|
else:
|
|
155
162
|
# NOTE: self.authenticator on reply packet is initialized
|
|
156
163
|
# with request authenticator by design.
|
|
@@ -197,9 +204,9 @@ class Packet(OrderedDict):
|
|
|
197
204
|
# attributes exactly as sent.
|
|
198
205
|
if self.raw_packet:
|
|
199
206
|
attr = self.raw_packet[20:]
|
|
200
|
-
attr = attr.replace(prev_ma[0], 16 *
|
|
207
|
+
attr = attr.replace(prev_ma[0], 16 * b'\00')
|
|
201
208
|
else:
|
|
202
|
-
self['Message-Authenticator'] = 16 *
|
|
209
|
+
self['Message-Authenticator'] = 16 * b'\00'
|
|
203
210
|
attr = self._PktEncodeAttributes()
|
|
204
211
|
|
|
205
212
|
header = struct.pack('!BBH', self.code, self.id,
|
|
@@ -211,7 +218,7 @@ class Packet(OrderedDict):
|
|
|
211
218
|
CoARequest, AccountingResponse):
|
|
212
219
|
if original_code is None or original_code != StatusServer:
|
|
213
220
|
# TODO: Handle Status-Server response correctly.
|
|
214
|
-
hmac_constructor.update(16 *
|
|
221
|
+
hmac_constructor.update(16 * b'\00')
|
|
215
222
|
elif self.code in (AccessAccept, AccessChallenge,
|
|
216
223
|
AccessReject):
|
|
217
224
|
if original_authenticator is None:
|
|
@@ -241,6 +248,10 @@ class Packet(OrderedDict):
|
|
|
241
248
|
**attributes)
|
|
242
249
|
|
|
243
250
|
def _DecodeValue(self, attr, value):
|
|
251
|
+
if attr.encrypt == 2:
|
|
252
|
+
# salt decrypt attribute
|
|
253
|
+
value = self.SaltDecrypt(value)
|
|
254
|
+
|
|
244
255
|
if attr.values.HasBackward(value):
|
|
245
256
|
return attr.values.GetBackward(value)
|
|
246
257
|
else:
|
|
@@ -283,7 +294,7 @@ class Packet(OrderedDict):
|
|
|
283
294
|
return key
|
|
284
295
|
|
|
285
296
|
attr = self.dict.attributes[key]
|
|
286
|
-
if attr.vendor and not attr.is_sub_attribute: #sub attribute keys don't need vendor
|
|
297
|
+
if attr.vendor and not attr.is_sub_attribute: # sub attribute keys don't need vendor
|
|
287
298
|
return (self.dict.vendors.GetForward(attr.vendor), attr.code)
|
|
288
299
|
else:
|
|
289
300
|
return attr.code
|
|
@@ -324,7 +335,7 @@ class Packet(OrderedDict):
|
|
|
324
335
|
return res
|
|
325
336
|
|
|
326
337
|
def __getitem__(self, key):
|
|
327
|
-
if not isinstance(key,
|
|
338
|
+
if not isinstance(key, str):
|
|
328
339
|
return OrderedDict.__getitem__(self, key)
|
|
329
340
|
|
|
330
341
|
values = OrderedDict.__getitem__(self, self._EncodeKey(key))
|
|
@@ -355,7 +366,7 @@ class Packet(OrderedDict):
|
|
|
355
366
|
OrderedDict.__delitem__(self, self._EncodeKey(key))
|
|
356
367
|
|
|
357
368
|
def __setitem__(self, key, item):
|
|
358
|
-
if isinstance(key,
|
|
369
|
+
if isinstance(key, str):
|
|
359
370
|
(key, item) = self._EncodeKeyValues(key, item)
|
|
360
371
|
OrderedDict.__setitem__(self, key, item)
|
|
361
372
|
else:
|
|
@@ -374,14 +385,10 @@ class Packet(OrderedDict):
|
|
|
374
385
|
:return: valid packet authenticator
|
|
375
386
|
:rtype: binary string
|
|
376
387
|
"""
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if six.PY3:
|
|
382
|
-
return bytes(data)
|
|
383
|
-
else:
|
|
384
|
-
return ''.join(chr(b) for b in data)
|
|
388
|
+
return bytes(
|
|
389
|
+
random_generator.randrange(0, 256)
|
|
390
|
+
for _ in range(16)
|
|
391
|
+
)
|
|
385
392
|
|
|
386
393
|
def CreateID(self):
|
|
387
394
|
"""Create a packet ID. All RADIUS requests have a ID which is used to
|
|
@@ -403,8 +410,8 @@ class Packet(OrderedDict):
|
|
|
403
410
|
:return: raw packet
|
|
404
411
|
:rtype: string
|
|
405
412
|
"""
|
|
406
|
-
assert(self.authenticator)
|
|
407
|
-
assert(self.secret is not None)
|
|
413
|
+
assert (self.authenticator)
|
|
414
|
+
assert (self.secret is not None)
|
|
408
415
|
|
|
409
416
|
if self.message_authenticator:
|
|
410
417
|
self._refresh_message_authenticator()
|
|
@@ -417,14 +424,14 @@ class Packet(OrderedDict):
|
|
|
417
424
|
|
|
418
425
|
return header + authenticator + attr
|
|
419
426
|
|
|
420
|
-
def VerifyReply(self, reply, rawreply=None):
|
|
427
|
+
def VerifyReply(self, reply, rawreply=None, enforce_ma=False):
|
|
421
428
|
if reply.id != self.id:
|
|
422
429
|
return False
|
|
423
430
|
|
|
424
431
|
if rawreply is None:
|
|
425
432
|
rawreply = reply.ReplyPacket()
|
|
426
433
|
|
|
427
|
-
|
|
434
|
+
_ = reply._PktEncodeAttributes()
|
|
428
435
|
# The Authenticator field in an Accounting-Response packet is called
|
|
429
436
|
# the Response Authenticator, and contains a one-way MD5 hash
|
|
430
437
|
# calculated over a stream of octets consisting of the Accounting
|
|
@@ -438,6 +445,13 @@ class Packet(OrderedDict):
|
|
|
438
445
|
|
|
439
446
|
if hash != rawreply[4:20]:
|
|
440
447
|
return False
|
|
448
|
+
|
|
449
|
+
if enforce_ma:
|
|
450
|
+
if self.message_authenticator is None:
|
|
451
|
+
return False
|
|
452
|
+
if not self.verify_message_authenticator():
|
|
453
|
+
return False
|
|
454
|
+
|
|
441
455
|
return True
|
|
442
456
|
|
|
443
457
|
def _PktEncodeAttribute(self, key, value):
|
|
@@ -450,11 +464,11 @@ class Packet(OrderedDict):
|
|
|
450
464
|
|
|
451
465
|
def _PktEncodeTlv(self, tlv_key, tlv_value):
|
|
452
466
|
tlv_attr = self.dict.attributes[self._DecodeKey(tlv_key)]
|
|
453
|
-
curr_avp =
|
|
467
|
+
curr_avp = b''
|
|
454
468
|
avps = []
|
|
455
469
|
max_sub_attribute_len = max(map(lambda item: len(item[1]), tlv_value.items()))
|
|
456
470
|
for i in range(max_sub_attribute_len):
|
|
457
|
-
sub_attr_encoding =
|
|
471
|
+
sub_attr_encoding = b''
|
|
458
472
|
for (code, datalst) in tlv_value.items():
|
|
459
473
|
if i < len(datalst):
|
|
460
474
|
sub_attr_encoding += self._PktEncodeAttribute(code, datalst[i])
|
|
@@ -470,7 +484,7 @@ class Packet(OrderedDict):
|
|
|
470
484
|
value = struct.pack('!BB', tlv_attr.code, (len(avp) + 2)) + avp
|
|
471
485
|
tlv_avps.append(value)
|
|
472
486
|
if tlv_attr.vendor:
|
|
473
|
-
vendor_avps =
|
|
487
|
+
vendor_avps = b''
|
|
474
488
|
for avp in tlv_avps:
|
|
475
489
|
vendor_avps += struct.pack(
|
|
476
490
|
'!BBL', 26, (len(avp) + 6),
|
|
@@ -481,10 +495,9 @@ class Packet(OrderedDict):
|
|
|
481
495
|
return b''.join(tlv_avps)
|
|
482
496
|
|
|
483
497
|
def _PktEncodeAttributes(self):
|
|
484
|
-
result =
|
|
498
|
+
result = b''
|
|
485
499
|
for (code, datalst) in self.items():
|
|
486
|
-
|
|
487
|
-
if attribute and attribute.type == 'tlv':
|
|
500
|
+
if self._PktIsTlvAttribute(code):
|
|
488
501
|
result += self._PktEncodeTlv(code, datalst)
|
|
489
502
|
else:
|
|
490
503
|
for data in datalst:
|
|
@@ -498,21 +511,20 @@ class Packet(OrderedDict):
|
|
|
498
511
|
return [(26, data)]
|
|
499
512
|
|
|
500
513
|
(vendor, atype, length) = struct.unpack('!LBB', data[:6])[0:3]
|
|
501
|
-
attribute = self.dict.attributes.get(self._DecodeKey((vendor, atype)))
|
|
502
514
|
try:
|
|
503
|
-
if
|
|
515
|
+
if self._PktIsTlvAttribute((vendor, atype)):
|
|
504
516
|
self._PktDecodeTlvAttribute((vendor, atype), data[6:length + 4])
|
|
505
517
|
tlvs = [] # tlv is added to the packet inside _PktDecodeTlvAttribute
|
|
506
518
|
else:
|
|
507
519
|
tlvs = [((vendor, atype), data[6:length + 4])]
|
|
508
|
-
except:
|
|
520
|
+
except Exception:
|
|
509
521
|
return [(26, data)]
|
|
510
522
|
|
|
511
523
|
sumlength = 4 + length
|
|
512
524
|
while len(data) > sumlength:
|
|
513
525
|
try:
|
|
514
526
|
atype, length = struct.unpack('!BB', data[sumlength:sumlength+2])[0:2]
|
|
515
|
-
except:
|
|
527
|
+
except Exception:
|
|
516
528
|
return [(26, data)]
|
|
517
529
|
tlvs.append(((vendor, atype), data[sumlength+2:sumlength+length]))
|
|
518
530
|
sumlength += length
|
|
@@ -527,6 +539,10 @@ class Packet(OrderedDict):
|
|
|
527
539
|
sub_attributes.setdefault(atype, []).append(data[loc+2:loc+length])
|
|
528
540
|
loc += length
|
|
529
541
|
|
|
542
|
+
def _PktIsTlvAttribute(self, code):
|
|
543
|
+
attr = self.dict.attributes.get(self._DecodeKey(code))
|
|
544
|
+
return (attr is not None and attr.type == 'tlv')
|
|
545
|
+
|
|
530
546
|
def DecodePacket(self, packet):
|
|
531
547
|
"""Initialize the object from raw packet data. Decode a packet as
|
|
532
548
|
received from the network and decode it.
|
|
@@ -555,11 +571,9 @@ class Packet(OrderedDict):
|
|
|
555
571
|
raise PacketError('Attribute header is corrupt')
|
|
556
572
|
|
|
557
573
|
if attrlen < 2:
|
|
558
|
-
raise PacketError(
|
|
559
|
-
'Attribute length is too small (%d)' % attrlen)
|
|
574
|
+
raise PacketError('Attribute length is too small (%d)' % attrlen)
|
|
560
575
|
|
|
561
576
|
value = packet[2:attrlen]
|
|
562
|
-
attribute = self.dict.attributes.get(self._DecodeKey(key))
|
|
563
577
|
if key == 26:
|
|
564
578
|
for (key, value) in self._PktDecodeVendorAttribute(value):
|
|
565
579
|
self.setdefault(key, []).append(value)
|
|
@@ -567,63 +581,82 @@ class Packet(OrderedDict):
|
|
|
567
581
|
# POST: Message Authenticator AVP is present.
|
|
568
582
|
self.message_authenticator = True
|
|
569
583
|
self.setdefault(key, []).append(value)
|
|
570
|
-
elif
|
|
571
|
-
self._PktDecodeTlvAttribute(key,value)
|
|
584
|
+
elif self._PktIsTlvAttribute(key):
|
|
585
|
+
self._PktDecodeTlvAttribute(key, value)
|
|
572
586
|
else:
|
|
573
587
|
self.setdefault(key, []).append(value)
|
|
574
588
|
|
|
575
589
|
packet = packet[attrlen:]
|
|
576
590
|
|
|
591
|
+
def _salt_en_decrypt(self, data, salt):
|
|
592
|
+
result = b''
|
|
593
|
+
if self.request_authenticator is not None:
|
|
594
|
+
last = self.request_authenticator + salt
|
|
595
|
+
else:
|
|
596
|
+
last = self.authenticator + salt
|
|
597
|
+
while data:
|
|
598
|
+
hash = md5_constructor(self.secret + last).digest()
|
|
599
|
+
for i in range(16):
|
|
600
|
+
result += bytes((hash[i] ^ data[i],))
|
|
601
|
+
|
|
602
|
+
last = result[-16:]
|
|
603
|
+
data = data[16:]
|
|
604
|
+
return result
|
|
605
|
+
|
|
577
606
|
def SaltCrypt(self, value):
|
|
578
|
-
"""
|
|
607
|
+
"""SaltEncrypt
|
|
579
608
|
|
|
580
609
|
:param value: plaintext value
|
|
581
|
-
:type
|
|
610
|
+
:type: unicode string
|
|
582
611
|
:return: obfuscated version of the value
|
|
583
612
|
:rtype: binary string
|
|
584
613
|
"""
|
|
585
614
|
|
|
586
|
-
if isinstance(value,
|
|
615
|
+
if isinstance(value, str):
|
|
587
616
|
value = value.encode('utf-8')
|
|
588
617
|
|
|
589
618
|
if self.authenticator is None:
|
|
590
619
|
# self.authenticator = self.CreateAuthenticator()
|
|
591
|
-
self.authenticator = 16 *
|
|
620
|
+
self.authenticator = 16 * b'\x00'
|
|
592
621
|
|
|
622
|
+
# create salt
|
|
593
623
|
random_value = 32768 + random_generator.randrange(0, 32767)
|
|
594
|
-
|
|
595
|
-
salt_raw = struct.pack('!H', random_value )
|
|
596
|
-
salt = chr(salt_raw[0]) + chr(salt_raw[1])
|
|
597
|
-
else:
|
|
598
|
-
salt = struct.pack('!H', random_value )
|
|
599
|
-
salt = chr(ord(salt[0]) | 1 << 7)+salt[1]
|
|
600
|
-
|
|
601
|
-
result = six.b(salt)
|
|
624
|
+
salt_raw = struct.pack('!H', random_value)
|
|
602
625
|
|
|
626
|
+
# length prefixing
|
|
603
627
|
length = struct.pack("B", len(value))
|
|
604
|
-
|
|
605
|
-
if len(buf) % 16 != 0:
|
|
606
|
-
buf += six.b('\x00') * (16 - (len(buf) % 16))
|
|
628
|
+
value = length + value
|
|
607
629
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if six.PY3:
|
|
612
|
-
for i in range(16):
|
|
613
|
-
result += bytes((hash[i] ^ buf[i],))
|
|
614
|
-
else:
|
|
615
|
-
for i in range(16):
|
|
616
|
-
result += chr(ord(hash[i]) ^ ord(buf[i]))
|
|
630
|
+
# zero padding
|
|
631
|
+
if len(value) % 16 != 0:
|
|
632
|
+
value += b'\x00' * (16 - (len(value) % 16))
|
|
617
633
|
|
|
618
|
-
|
|
619
|
-
buf = buf[16:]
|
|
634
|
+
return salt_raw + self._salt_en_decrypt(value, salt_raw)
|
|
620
635
|
|
|
621
|
-
|
|
636
|
+
def SaltDecrypt(self, value):
|
|
637
|
+
""" SaltDecrypt
|
|
638
|
+
|
|
639
|
+
:param value: encrypted value including salt
|
|
640
|
+
:type: binary string
|
|
641
|
+
:return: decrypted plaintext string
|
|
642
|
+
:rtype: unicode string
|
|
643
|
+
"""
|
|
644
|
+
# extract salt
|
|
645
|
+
salt = value[:2]
|
|
646
|
+
|
|
647
|
+
# decrypt
|
|
648
|
+
value = self._salt_en_decrypt(value[2:], salt)
|
|
649
|
+
|
|
650
|
+
# remove padding
|
|
651
|
+
length = value[0]
|
|
652
|
+
value = value[1:length+1]
|
|
653
|
+
|
|
654
|
+
return value
|
|
622
655
|
|
|
623
656
|
|
|
624
657
|
class AuthPacket(Packet):
|
|
625
|
-
def __init__(self, code=AccessRequest, id=None, secret=
|
|
626
|
-
|
|
658
|
+
def __init__(self, code=AccessRequest, id=None, secret=b'',
|
|
659
|
+
authenticator=None, auth_type='pap', **attributes):
|
|
627
660
|
"""Constructor
|
|
628
661
|
|
|
629
662
|
:param code: packet type code
|
|
@@ -692,34 +725,39 @@ class AuthPacket(Packet):
|
|
|
692
725
|
return header + attr
|
|
693
726
|
|
|
694
727
|
def PwDecrypt(self, password):
|
|
695
|
-
"""Obfuscate a RADIUS password. RADIUS hides passwords in packets by
|
|
728
|
+
"""De-Obfuscate a RADIUS password. RADIUS hides passwords in packets by
|
|
696
729
|
using an algorithm based on the MD5 hash of the packet authenticator
|
|
697
730
|
and RADIUS secret. This function reverses the obfuscation process.
|
|
698
731
|
|
|
732
|
+
Although RFC2865 does not explicitly state UTF-8 for the password field,
|
|
733
|
+
the rest of RFC2865 defines UTF-8 as the encoding expected for the decrypted password.
|
|
734
|
+
|
|
735
|
+
|
|
699
736
|
:param password: obfuscated form of password
|
|
700
737
|
:type password: binary string
|
|
701
738
|
:return: plaintext password
|
|
702
739
|
:rtype: unicode string
|
|
703
740
|
"""
|
|
704
741
|
buf = password
|
|
705
|
-
pw =
|
|
742
|
+
pw = b''
|
|
706
743
|
|
|
707
744
|
last = self.authenticator
|
|
708
745
|
while buf:
|
|
709
746
|
hash = md5_constructor(self.secret + last).digest()
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
pw += bytes((hash[i] ^ buf[i],))
|
|
713
|
-
else:
|
|
714
|
-
for i in range(16):
|
|
715
|
-
pw += chr(ord(hash[i]) ^ ord(buf[i]))
|
|
716
|
-
|
|
747
|
+
for i in range(16):
|
|
748
|
+
pw += bytes((hash[i] ^ buf[i],))
|
|
717
749
|
(last, buf) = (buf[:16], buf[16:])
|
|
718
750
|
|
|
719
|
-
|
|
751
|
+
# This is safe even with UTF-8 encoding since no valid encoding of UTF-8
|
|
752
|
+
# (other than encoding U+0000 NULL) will produce a bytestream containing 0x00 byte.
|
|
753
|
+
while pw.endswith(b'\x00'):
|
|
720
754
|
pw = pw[:-1]
|
|
721
755
|
|
|
722
|
-
|
|
756
|
+
# If the shared secret with the client is not the same, then de-obfuscating the password
|
|
757
|
+
# field may yield illegal UTF-8 bytes. Therefore, in order not to provoke an Exception here
|
|
758
|
+
# (which would be not consistently generated since this will depend on the random data
|
|
759
|
+
# chosen by the client) we simply ignore un-parsable UTF-8 sequences.
|
|
760
|
+
return pw.decode('utf-8', errors="ignore")
|
|
723
761
|
|
|
724
762
|
def PwCrypt(self, password):
|
|
725
763
|
"""Obfuscate password.
|
|
@@ -738,25 +776,20 @@ class AuthPacket(Packet):
|
|
|
738
776
|
if self.authenticator is None:
|
|
739
777
|
self.authenticator = self.CreateAuthenticator()
|
|
740
778
|
|
|
741
|
-
if isinstance(password,
|
|
779
|
+
if isinstance(password, str):
|
|
742
780
|
password = password.encode('utf-8')
|
|
743
781
|
|
|
744
782
|
buf = password
|
|
745
783
|
if len(password) % 16 != 0:
|
|
746
|
-
buf +=
|
|
784
|
+
buf += b'\x00' * (16 - (len(password) % 16))
|
|
747
785
|
|
|
748
|
-
result =
|
|
786
|
+
result = b''
|
|
749
787
|
|
|
750
788
|
last = self.authenticator
|
|
751
789
|
while buf:
|
|
752
790
|
hash = md5_constructor(self.secret + last).digest()
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
result += bytes((hash[i] ^ buf[i],))
|
|
756
|
-
else:
|
|
757
|
-
for i in range(16):
|
|
758
|
-
result += chr(ord(hash[i]) ^ ord(buf[i]))
|
|
759
|
-
|
|
791
|
+
for i in range(16):
|
|
792
|
+
result += bytes((hash[i] ^ buf[i],))
|
|
760
793
|
last = result[-16:]
|
|
761
794
|
buf = buf[16:]
|
|
762
795
|
|
|
@@ -774,16 +807,14 @@ class AuthPacket(Packet):
|
|
|
774
807
|
if not self.authenticator:
|
|
775
808
|
self.authenticator = self.CreateAuthenticator()
|
|
776
809
|
|
|
777
|
-
if isinstance(userpwd,
|
|
810
|
+
if isinstance(userpwd, str):
|
|
778
811
|
userpwd = userpwd.strip().encode('utf-8')
|
|
779
812
|
|
|
780
813
|
chap_password = tools.DecodeOctets(self.get(3)[0])
|
|
781
814
|
if len(chap_password) != 17:
|
|
782
815
|
return False
|
|
783
816
|
|
|
784
|
-
chapid = chap_password[
|
|
785
|
-
if six.PY3:
|
|
786
|
-
chapid = chr(chapid).encode('utf-8')
|
|
817
|
+
chapid = chap_password[:1]
|
|
787
818
|
password = chap_password[1:]
|
|
788
819
|
|
|
789
820
|
challenge = self.authenticator
|
|
@@ -794,11 +825,11 @@ class AuthPacket(Packet):
|
|
|
794
825
|
def VerifyAuthRequest(self):
|
|
795
826
|
"""Verify request authenticator.
|
|
796
827
|
|
|
797
|
-
:return: True if verification
|
|
828
|
+
:return: True if verification passed else False
|
|
798
829
|
:rtype: boolean
|
|
799
830
|
"""
|
|
800
|
-
assert(self.raw_packet)
|
|
801
|
-
hash = md5_constructor(self.raw_packet[0:4] + 16 *
|
|
831
|
+
assert (self.raw_packet)
|
|
832
|
+
hash = md5_constructor(self.raw_packet[0:4] + 16 * b'\x00' +
|
|
802
833
|
self.raw_packet[20:] + self.secret).digest()
|
|
803
834
|
return hash == self.authenticator
|
|
804
835
|
|
|
@@ -808,7 +839,7 @@ class AcctPacket(Packet):
|
|
|
808
839
|
of the generic :obj:`Packet` class for accounting packets.
|
|
809
840
|
"""
|
|
810
841
|
|
|
811
|
-
def __init__(self, code=AccountingRequest, id=None, secret=
|
|
842
|
+
def __init__(self, code=AccountingRequest, id=None, secret=b'',
|
|
812
843
|
authenticator=None, **attributes):
|
|
813
844
|
"""Constructor
|
|
814
845
|
|
|
@@ -837,12 +868,12 @@ class AcctPacket(Packet):
|
|
|
837
868
|
def VerifyAcctRequest(self):
|
|
838
869
|
"""Verify request authenticator.
|
|
839
870
|
|
|
840
|
-
:return:
|
|
871
|
+
:return: True if verification passed else False
|
|
841
872
|
:rtype: boolean
|
|
842
873
|
"""
|
|
843
|
-
assert(self.raw_packet)
|
|
874
|
+
assert (self.raw_packet)
|
|
844
875
|
|
|
845
|
-
hash = md5_constructor(self.raw_packet[0:4] + 16 *
|
|
876
|
+
hash = md5_constructor(self.raw_packet[0:4] + 16 * b'\x00' +
|
|
846
877
|
self.raw_packet[20:] + self.secret).digest()
|
|
847
878
|
|
|
848
879
|
return hash == self.authenticator
|
|
@@ -864,7 +895,7 @@ class AcctPacket(Packet):
|
|
|
864
895
|
|
|
865
896
|
attr = self._PktEncodeAttributes()
|
|
866
897
|
header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
|
|
867
|
-
self.authenticator = md5_constructor(header[0:4] + 16 *
|
|
898
|
+
self.authenticator = md5_constructor(header[0:4] + 16 * b'\x00' +
|
|
868
899
|
attr + self.secret).digest()
|
|
869
900
|
|
|
870
901
|
ans = header + self.authenticator + attr
|
|
@@ -877,8 +908,8 @@ class CoAPacket(Packet):
|
|
|
877
908
|
of the generic :obj:`Packet` class for CoA packets.
|
|
878
909
|
"""
|
|
879
910
|
|
|
880
|
-
def __init__(self, code=CoARequest, id=None, secret=
|
|
881
|
-
|
|
911
|
+
def __init__(self, code=CoARequest, id=None, secret=b'',
|
|
912
|
+
authenticator=None, **attributes):
|
|
882
913
|
"""Constructor
|
|
883
914
|
|
|
884
915
|
:param dict: RADIUS dictionary
|
|
@@ -906,11 +937,11 @@ class CoAPacket(Packet):
|
|
|
906
937
|
def VerifyCoARequest(self):
|
|
907
938
|
"""Verify request authenticator.
|
|
908
939
|
|
|
909
|
-
:return:
|
|
940
|
+
:return: True if verification passed else False
|
|
910
941
|
:rtype: boolean
|
|
911
942
|
"""
|
|
912
|
-
assert(self.raw_packet)
|
|
913
|
-
hash = md5_constructor(self.raw_packet[0:4] + 16 *
|
|
943
|
+
assert (self.raw_packet)
|
|
944
|
+
hash = md5_constructor(self.raw_packet[0:4] + 16 * b'\x00' +
|
|
914
945
|
self.raw_packet[20:] + self.secret).digest()
|
|
915
946
|
return hash == self.authenticator
|
|
916
947
|
|
|
@@ -929,13 +960,13 @@ class CoAPacket(Packet):
|
|
|
929
960
|
self.id = self.CreateID()
|
|
930
961
|
|
|
931
962
|
header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
|
|
932
|
-
self.authenticator = md5_constructor(header[0:4] + 16 *
|
|
963
|
+
self.authenticator = md5_constructor(header[0:4] + 16 * b'\x00' +
|
|
933
964
|
attr + self.secret).digest()
|
|
934
965
|
|
|
935
966
|
if self.message_authenticator:
|
|
936
967
|
self._refresh_message_authenticator()
|
|
937
968
|
attr = self._PktEncodeAttributes()
|
|
938
|
-
self.authenticator = md5_constructor(header[0:4] + 16 *
|
|
969
|
+
self.authenticator = md5_constructor(header[0:4] + 16 * b'\x00' +
|
|
939
970
|
attr + self.secret).digest()
|
|
940
971
|
|
|
941
972
|
return header + self.authenticator + attr
|
pyrad/proxy.py
CHANGED
|
@@ -25,7 +25,7 @@ class Proxy(Server):
|
|
|
25
25
|
self._proxyfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
26
26
|
self._fdmap[self._proxyfd.fileno()] = self._proxyfd
|
|
27
27
|
self._poll.register(self._proxyfd.fileno(),
|
|
28
|
-
|
|
28
|
+
(select.POLLIN | select.POLLPRI | select.POLLERR))
|
|
29
29
|
|
|
30
30
|
def _HandleProxyPacket(self, pkt):
|
|
31
31
|
"""Process a packet received on the reply socket.
|
|
@@ -41,7 +41,7 @@ class Proxy(Server):
|
|
|
41
41
|
pkt.secret = self.hosts[pkt.source[0]].secret
|
|
42
42
|
|
|
43
43
|
if pkt.code not in [packet.AccessAccept, packet.AccessReject,
|
|
44
|
-
|
|
44
|
+
packet.AccountingResponse]:
|
|
45
45
|
raise ServerPacketError('Received non-response on proxy socket')
|
|
46
46
|
|
|
47
47
|
def _ProcessInput(self, fd):
|
pyrad/server.py
CHANGED
|
@@ -114,7 +114,7 @@ class Server(host.Host):
|
|
|
114
114
|
"""
|
|
115
115
|
results = set()
|
|
116
116
|
try:
|
|
117
|
-
tmp = socket.getaddrinfo(addr,
|
|
117
|
+
tmp = socket.getaddrinfo(addr, 80)
|
|
118
118
|
except socket.gaierror:
|
|
119
119
|
return []
|
|
120
120
|
|
|
@@ -123,10 +123,9 @@ class Server(host.Host):
|
|
|
123
123
|
|
|
124
124
|
return results
|
|
125
125
|
|
|
126
|
-
|
|
127
126
|
def BindToAddress(self, addr):
|
|
128
|
-
"""Add an address to listen
|
|
129
|
-
|
|
127
|
+
"""Add an address to listen on a specific interface.
|
|
128
|
+
String "0.0.0.0" indicates you want to listen on all interfaces.
|
|
130
129
|
|
|
131
130
|
:param addr: IP address to listen on
|
|
132
131
|
:type addr: string
|
|
@@ -151,7 +150,6 @@ class Server(host.Host):
|
|
|
151
150
|
coafd.bind((address, self.coaport))
|
|
152
151
|
self.coafds.append(coafd)
|
|
153
152
|
|
|
154
|
-
|
|
155
153
|
def HandleAuthPacket(self, pkt):
|
|
156
154
|
"""Authentication packet handler.
|
|
157
155
|
This is an empty function that is called when a valid
|
|
@@ -247,7 +245,6 @@ class Server(host.Host):
|
|
|
247
245
|
:type pkt: Packet class instance
|
|
248
246
|
"""
|
|
249
247
|
self._AddSecret(pkt)
|
|
250
|
-
pkt.secret = self.hosts[pkt.source[0]].secret
|
|
251
248
|
if pkt.code == packet.CoARequest:
|
|
252
249
|
self.HandleCoaPacket(pkt)
|
|
253
250
|
elif pkt.code == packet.DisconnectRequest:
|
|
@@ -255,7 +252,6 @@ class Server(host.Host):
|
|
|
255
252
|
else:
|
|
256
253
|
raise ServerPacketError('Received non-coa packet on coa port')
|
|
257
254
|
|
|
258
|
-
|
|
259
255
|
def _GrabPacket(self, pktgen, fd):
|
|
260
256
|
"""Read a packet from a network connection.
|
|
261
257
|
This method assumes there is data waiting for to be read.
|