pyrad 2.3__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 +54 -9
- pyrad/client_async.py +22 -14
- pyrad/dictfile.py +2 -5
- pyrad/dictionary.py +12 -1
- pyrad/host.py +1 -1
- pyrad/packet.py +208 -133
- pyrad/proxy.py +2 -2
- pyrad/server.py +3 -7
- pyrad/server_async.py +4 -5
- pyrad/tests/__init__.py +2 -2
- pyrad/tests/mock.py +5 -1
- pyrad/tests/{testBidict.py → test_bidict.py} +2 -2
- pyrad/tests/{testClient.py → test_client.py} +28 -30
- pyrad/tests/{testDictionary.py → test_dictionary.py} +38 -21
- pyrad/tests/{testHost.py → test_host.py} +10 -10
- pyrad/tests/test_packet.py +679 -0
- pyrad/tests/{testProxy.py → test_proxy.py} +11 -11
- pyrad/tests/{testServer.py → test_server.py} +35 -33
- pyrad/tests/test_tools.py +126 -0
- pyrad/tools.py +254 -158
- {pyrad-2.3.dist-info → pyrad-2.5.0.dist-info}/METADATA +44 -20
- pyrad-2.5.0.dist-info/RECORD +51 -0
- {pyrad-2.3.dist-info → pyrad-2.5.0.dist-info}/WHEEL +1 -1
- {pyrad-2.3.dist-info → pyrad-2.5.0.dist-info/licenses}/LICENSE.txt +2 -1
- pyrad-2.5.0.dist-info/top_level.txt +3 -0
- pyrad/tests/testPacket.py +0 -530
- pyrad/tests/testTools.py +0 -122
- pyrad-2.3.dist-info/RECORD +0 -29
- pyrad-2.3.dist-info/top_level.txt +0 -1
- {pyrad-2.3.dist-info → pyrad-2.5.0.dist-info}/zip-safe +0 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import hmac
|
|
2
|
+
import os
|
|
3
|
+
import struct
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from . import home
|
|
7
|
+
|
|
8
|
+
from collections import OrderedDict
|
|
9
|
+
from pyrad import packet
|
|
10
|
+
from pyrad.client import Client
|
|
11
|
+
from pyrad.dictionary import Dictionary
|
|
12
|
+
try:
|
|
13
|
+
import hashlib
|
|
14
|
+
md5_constructor = hashlib.md5
|
|
15
|
+
except ImportError:
|
|
16
|
+
# BBB for python 2.4
|
|
17
|
+
import md5
|
|
18
|
+
md5_constructor = md5.new
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UtilityTests(unittest.TestCase):
|
|
22
|
+
def testGenerateID(self):
|
|
23
|
+
id = packet.CreateID()
|
|
24
|
+
self.assertTrue(isinstance(id, int))
|
|
25
|
+
newid = packet.CreateID()
|
|
26
|
+
self.assertNotEqual(id, newid)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PacketConstructionTests(unittest.TestCase):
|
|
30
|
+
klass = packet.Packet
|
|
31
|
+
|
|
32
|
+
def setUp(self):
|
|
33
|
+
self.path = os.path.join(home, 'data')
|
|
34
|
+
self.dict = Dictionary(os.path.join(self.path, 'simple'))
|
|
35
|
+
|
|
36
|
+
def testBasicConstructor(self):
|
|
37
|
+
pkt = self.klass()
|
|
38
|
+
self.assertTrue(isinstance(pkt.code, int))
|
|
39
|
+
self.assertTrue(isinstance(pkt.id, int))
|
|
40
|
+
self.assertTrue(isinstance(pkt.secret, bytes))
|
|
41
|
+
|
|
42
|
+
def testNamedConstructor(self):
|
|
43
|
+
pkt = self.klass(code=26, id=38, secret=b'secret',
|
|
44
|
+
authenticator=b'authenticator',
|
|
45
|
+
dict='fakedict')
|
|
46
|
+
self.assertEqual(pkt.code, 26)
|
|
47
|
+
self.assertEqual(pkt.id, 38)
|
|
48
|
+
self.assertEqual(pkt.secret, b'secret')
|
|
49
|
+
self.assertEqual(pkt.authenticator, b'authenticator')
|
|
50
|
+
self.assertEqual(pkt.dict, 'fakedict')
|
|
51
|
+
|
|
52
|
+
def testConstructWithDictionary(self):
|
|
53
|
+
pkt = self.klass(dict=self.dict)
|
|
54
|
+
self.assertTrue(pkt.dict is self.dict)
|
|
55
|
+
|
|
56
|
+
def testConstructorIgnoredParameters(self):
|
|
57
|
+
marker = []
|
|
58
|
+
pkt = self.klass(fd=marker)
|
|
59
|
+
self.assertFalse(getattr(pkt, 'fd', None) is marker)
|
|
60
|
+
|
|
61
|
+
def testSecretMustBeBytestring(self):
|
|
62
|
+
self.assertRaises(TypeError, self.klass, secret='secret')
|
|
63
|
+
|
|
64
|
+
def testConstructorWithAttributes(self):
|
|
65
|
+
pkt = self.klass(**{'Test-String': 'this works', 'dict': self.dict})
|
|
66
|
+
self.assertEqual(pkt['Test-String'], ['this works'])
|
|
67
|
+
|
|
68
|
+
def testConstructorWithTlvAttribute(self):
|
|
69
|
+
pkt = self.klass(**{
|
|
70
|
+
'Test-Tlv-Str': 'this works',
|
|
71
|
+
'Test-Tlv-Int': 10,
|
|
72
|
+
'dict': self.dict
|
|
73
|
+
})
|
|
74
|
+
self.assertEqual(
|
|
75
|
+
pkt['Test-Tlv'],
|
|
76
|
+
{'Test-Tlv-Str': ['this works'], 'Test-Tlv-Int': [10]}
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class PacketTests(unittest.TestCase):
|
|
81
|
+
def setUp(self):
|
|
82
|
+
self.path = os.path.join(home, 'data')
|
|
83
|
+
self.dict = Dictionary(os.path.join(self.path, 'full'))
|
|
84
|
+
self.packet = packet.Packet(
|
|
85
|
+
id=0, secret=b'secret',
|
|
86
|
+
authenticator=b'01234567890ABCDEF', dict=self.dict)
|
|
87
|
+
|
|
88
|
+
def _create_reply_with_duplicate_attributes(self, request):
|
|
89
|
+
"""
|
|
90
|
+
Creates a reply to the given request with multiple instances of the
|
|
91
|
+
same attribute that also do not appear sequentially in the list. Used
|
|
92
|
+
to ensure that methods providing authenticator and
|
|
93
|
+
Message-Authenticator verification can handle the case where multiple
|
|
94
|
+
instances of an given attribute do not appear sequentially in the
|
|
95
|
+
attributes list.
|
|
96
|
+
"""
|
|
97
|
+
# Manually build the packet since using packet.Packet will always group
|
|
98
|
+
# attributes of the same type together
|
|
99
|
+
attributes = self._get_attribute_bytes('Test-String', 'test')
|
|
100
|
+
attributes += self._get_attribute_bytes('Test-Integer', 1)
|
|
101
|
+
attributes += self._get_attribute_bytes('Test-String', 'test')
|
|
102
|
+
attributes += self._get_attribute_bytes('Message-Authenticator',
|
|
103
|
+
16 * b'\00')
|
|
104
|
+
|
|
105
|
+
header = struct.pack('!BBH', packet.AccessAccept,
|
|
106
|
+
request.id, (20 + len(attributes)))
|
|
107
|
+
|
|
108
|
+
# Calculate the Message-Authenticator and update the attribute
|
|
109
|
+
hmac_constructor = hmac.new(request.secret, None, md5_constructor)
|
|
110
|
+
hmac_constructor.update(header + request.authenticator + attributes)
|
|
111
|
+
updated_message_authenticator = hmac_constructor.digest()
|
|
112
|
+
attributes = attributes.replace(b'\x00' * 16,
|
|
113
|
+
updated_message_authenticator)
|
|
114
|
+
|
|
115
|
+
# Calculate the response authenticator
|
|
116
|
+
authenticator = md5_constructor(header
|
|
117
|
+
+ request.authenticator
|
|
118
|
+
+ attributes
|
|
119
|
+
+ request.secret).digest()
|
|
120
|
+
|
|
121
|
+
reply_bytes = header + authenticator + attributes
|
|
122
|
+
return packet.AuthPacket(packet=reply_bytes, dict=self.dict)
|
|
123
|
+
|
|
124
|
+
def _get_attribute_bytes(self, attr_name, value):
|
|
125
|
+
attr = self.dict.attributes[attr_name]
|
|
126
|
+
attr_key = attr.code
|
|
127
|
+
attr_value = packet.tools.EncodeAttr(attr.type, value)
|
|
128
|
+
attr_len = len(attr_value) + 2
|
|
129
|
+
return struct.pack('!BB', attr_key, attr_len) + attr_value
|
|
130
|
+
|
|
131
|
+
def testCreateReply(self):
|
|
132
|
+
reply = self.packet.CreateReply(**{'Test-Integer': 10})
|
|
133
|
+
self.assertEqual(reply.id, self.packet.id)
|
|
134
|
+
self.assertEqual(reply.secret, self.packet.secret)
|
|
135
|
+
self.assertEqual(reply.authenticator, self.packet.authenticator)
|
|
136
|
+
self.assertEqual(reply['Test-Integer'], [10])
|
|
137
|
+
|
|
138
|
+
def testAttributeAccess(self):
|
|
139
|
+
self.packet['Test-Integer'] = 10
|
|
140
|
+
self.assertEqual(self.packet['Test-Integer'], [10])
|
|
141
|
+
self.assertEqual(self.packet[3], [b'\x00\x00\x00\x0a'])
|
|
142
|
+
|
|
143
|
+
self.packet['Test-String'] = 'dummy'
|
|
144
|
+
self.assertEqual(self.packet['Test-String'], ['dummy'])
|
|
145
|
+
self.assertEqual(self.packet[1], [b'dummy'])
|
|
146
|
+
|
|
147
|
+
def testAttributeValueAccess(self):
|
|
148
|
+
self.packet['Test-Integer'] = 'Three'
|
|
149
|
+
self.assertEqual(self.packet['Test-Integer'], ['Three'])
|
|
150
|
+
self.assertEqual(self.packet[3], [b'\x00\x00\x00\x03'])
|
|
151
|
+
|
|
152
|
+
def testVendorAttributeAccess(self):
|
|
153
|
+
self.packet['Simplon-Number'] = 10
|
|
154
|
+
self.assertEqual(self.packet['Simplon-Number'], [10])
|
|
155
|
+
self.assertEqual(self.packet[(16, 1)], [b'\x00\x00\x00\x0a'])
|
|
156
|
+
|
|
157
|
+
self.packet['Simplon-Number'] = 'Four'
|
|
158
|
+
self.assertEqual(self.packet['Simplon-Number'], ['Four'])
|
|
159
|
+
self.assertEqual(self.packet[(16, 1)], [b'\x00\x00\x00\x04'])
|
|
160
|
+
|
|
161
|
+
def testRawAttributeAccess(self):
|
|
162
|
+
marker = [b'']
|
|
163
|
+
self.packet[1] = marker
|
|
164
|
+
self.assertTrue(self.packet[1] is marker)
|
|
165
|
+
self.packet[(16, 1)] = marker
|
|
166
|
+
self.assertTrue(self.packet[(16, 1)] is marker)
|
|
167
|
+
|
|
168
|
+
def testEncryptedAttributes(self):
|
|
169
|
+
self.packet['Test-Encrypted-String'] = 'dummy'
|
|
170
|
+
self.assertEqual(self.packet['Test-Encrypted-String'], ['dummy'])
|
|
171
|
+
self.packet['Test-Encrypted-Integer'] = 10
|
|
172
|
+
self.assertEqual(self.packet['Test-Encrypted-Integer'], [10])
|
|
173
|
+
|
|
174
|
+
def testHasKey(self):
|
|
175
|
+
self.assertEqual(self.packet.has_key('Test-String'), False)
|
|
176
|
+
self.assertEqual('Test-String' in self.packet, False)
|
|
177
|
+
self.packet['Test-String'] = 'dummy'
|
|
178
|
+
self.assertEqual(self.packet.has_key('Test-String'), True)
|
|
179
|
+
self.assertEqual(self.packet.has_key(1), True)
|
|
180
|
+
self.assertEqual(1 in self.packet, True)
|
|
181
|
+
|
|
182
|
+
def testHasKeyWithUnknownKey(self):
|
|
183
|
+
self.assertEqual(self.packet.has_key('Unknown-Attribute'), False)
|
|
184
|
+
self.assertEqual('Unknown-Attribute' in self.packet, False)
|
|
185
|
+
|
|
186
|
+
def testDelItem(self):
|
|
187
|
+
self.packet['Test-String'] = 'dummy'
|
|
188
|
+
del self.packet['Test-String']
|
|
189
|
+
self.assertEqual(self.packet.has_key('Test-String'), False)
|
|
190
|
+
self.packet['Test-String'] = 'dummy'
|
|
191
|
+
del self.packet[1]
|
|
192
|
+
self.assertEqual(self.packet.has_key('Test-String'), False)
|
|
193
|
+
|
|
194
|
+
def testKeys(self):
|
|
195
|
+
self.assertEqual(self.packet.keys(), [])
|
|
196
|
+
self.packet['Test-String'] = 'dummy'
|
|
197
|
+
self.assertEqual(self.packet.keys(), ['Test-String'])
|
|
198
|
+
self.packet['Test-Integer'] = 10
|
|
199
|
+
self.assertEqual(self.packet.keys(), ['Test-String', 'Test-Integer'])
|
|
200
|
+
OrderedDict.__setitem__(self.packet, 12345, None)
|
|
201
|
+
self.assertEqual(self.packet.keys(),
|
|
202
|
+
['Test-String', 'Test-Integer', 12345])
|
|
203
|
+
|
|
204
|
+
def testCreateAuthenticator(self):
|
|
205
|
+
a = packet.Packet.CreateAuthenticator()
|
|
206
|
+
self.assertTrue(isinstance(a, bytes))
|
|
207
|
+
self.assertEqual(len(a), 16)
|
|
208
|
+
|
|
209
|
+
b = packet.Packet.CreateAuthenticator()
|
|
210
|
+
self.assertNotEqual(a, b)
|
|
211
|
+
|
|
212
|
+
def testGenerateID(self):
|
|
213
|
+
id = self.packet.CreateID()
|
|
214
|
+
self.assertTrue(isinstance(id, int))
|
|
215
|
+
newid = self.packet.CreateID()
|
|
216
|
+
self.assertNotEqual(id, newid)
|
|
217
|
+
|
|
218
|
+
def testReplyPacket(self):
|
|
219
|
+
reply = self.packet.ReplyPacket()
|
|
220
|
+
self.assertEqual(reply,
|
|
221
|
+
(b'\x00\x00\x00\x14\xb0\x5e\x4b\xfb\xcc\x1c'
|
|
222
|
+
b'\x8c\x8e\xc4\x72\xac\xea\x87\x45\x63\xa7'))
|
|
223
|
+
|
|
224
|
+
def testVerifyReply(self):
|
|
225
|
+
reply = self.packet.CreateReply()
|
|
226
|
+
self.assertEqual(self.packet.VerifyReply(reply), True)
|
|
227
|
+
|
|
228
|
+
reply.id += 1
|
|
229
|
+
self.assertEqual(self.packet.VerifyReply(reply), False)
|
|
230
|
+
reply.id = self.packet.id
|
|
231
|
+
|
|
232
|
+
reply.secret = b'different'
|
|
233
|
+
self.assertEqual(self.packet.VerifyReply(reply), False)
|
|
234
|
+
reply.secret = self.packet.secret
|
|
235
|
+
|
|
236
|
+
reply.authenticator = b'X' * 16
|
|
237
|
+
self.assertEqual(self.packet.VerifyReply(reply), False)
|
|
238
|
+
reply.authenticator = self.packet.authenticator
|
|
239
|
+
|
|
240
|
+
def testVerifyReplyDuplicateAttributes(self):
|
|
241
|
+
reply = self._create_reply_with_duplicate_attributes(self.packet)
|
|
242
|
+
self.assertTrue(self.packet.VerifyReply(
|
|
243
|
+
reply=reply,
|
|
244
|
+
rawreply=reply.raw_packet))
|
|
245
|
+
|
|
246
|
+
def testVerifyMessageAuthenticator(self):
|
|
247
|
+
reply = self.packet.CreateReply(**{
|
|
248
|
+
'Test-String': 'test',
|
|
249
|
+
'Test-Integer': 3,
|
|
250
|
+
})
|
|
251
|
+
reply.code = packet.AccessAccept
|
|
252
|
+
reply.add_message_authenticator()
|
|
253
|
+
reply._refresh_message_authenticator()
|
|
254
|
+
self.assertTrue(reply.verify_message_authenticator(
|
|
255
|
+
secret=b'secret',
|
|
256
|
+
original_authenticator=self.packet.authenticator,
|
|
257
|
+
original_code=self.packet.code))
|
|
258
|
+
|
|
259
|
+
self.assertFalse(reply.verify_message_authenticator(
|
|
260
|
+
secret=b'bad_secret',
|
|
261
|
+
original_authenticator=self.packet.authenticator,
|
|
262
|
+
original_code=self.packet.code))
|
|
263
|
+
|
|
264
|
+
self.assertFalse(reply.verify_message_authenticator(
|
|
265
|
+
secret=b'secret',
|
|
266
|
+
original_authenticator=b'bad_authenticator',
|
|
267
|
+
original_code=self.packet.code))
|
|
268
|
+
|
|
269
|
+
def testVerifyMessageAuthenticatorDuplicateAttributes(self):
|
|
270
|
+
reply = self._create_reply_with_duplicate_attributes(self.packet)
|
|
271
|
+
self.assertTrue(reply.verify_message_authenticator(
|
|
272
|
+
secret=b'secret',
|
|
273
|
+
original_authenticator=self.packet.authenticator,
|
|
274
|
+
original_code=packet.AccessRequest))
|
|
275
|
+
|
|
276
|
+
def testPktEncodeAttribute(self):
|
|
277
|
+
encode = self.packet._PktEncodeAttribute
|
|
278
|
+
|
|
279
|
+
# Encode a normal attribute
|
|
280
|
+
self.assertEqual(
|
|
281
|
+
encode(1, b'value'),
|
|
282
|
+
b'\x01\x07value')
|
|
283
|
+
# Encode a vendor attribute
|
|
284
|
+
self.assertEqual(
|
|
285
|
+
encode((1, 2), b'value'),
|
|
286
|
+
b'\x1a\x0d\x00\x00\x00\x01\x02\x07value')
|
|
287
|
+
|
|
288
|
+
def testPktEncodeTlvAttribute(self):
|
|
289
|
+
encode = self.packet._PktEncodeTlv
|
|
290
|
+
|
|
291
|
+
# Encode a normal tlv attribute
|
|
292
|
+
self.assertEqual(
|
|
293
|
+
encode(4, {1: [b'value'], 2: [b'\x00\x00\x00\x02']}),
|
|
294
|
+
b'\x04\x0f\x01\x07value\x02\x06\x00\x00\x00\x02')
|
|
295
|
+
|
|
296
|
+
# Encode a normal tlv attribute with several sub attribute instances
|
|
297
|
+
self.assertEqual(
|
|
298
|
+
encode(4, {1: [b'value', b'other'], 2: [b'\x00\x00\x00\x02']}),
|
|
299
|
+
b'\x04\x16\x01\x07value\x02\x06\x00\x00\x00\x02\x01\x07other')
|
|
300
|
+
# Encode a vendor tlv attribute
|
|
301
|
+
self.assertEqual(
|
|
302
|
+
encode((16, 3), {1: [b'value'], 2: [b'\x00\x00\x00\x02']}),
|
|
303
|
+
b'\x1a\x15\x00\x00\x00\x10\x03\x0f\x01\x07value\x02\x06\x00\x00\x00\x02')
|
|
304
|
+
|
|
305
|
+
def testPktEncodeLongTlvAttribute(self):
|
|
306
|
+
encode = self.packet._PktEncodeTlv
|
|
307
|
+
|
|
308
|
+
long_str = b'a' * 245
|
|
309
|
+
# Encode a long tlv attribute - check it is split between AVPs
|
|
310
|
+
self.assertEqual(
|
|
311
|
+
encode(4, {1: [b'value', long_str], 2: [b'\x00\x00\x00\x02']}),
|
|
312
|
+
b'\x04\x0f\x01\x07value\x02\x06\x00\x00\x00\x02\x04\xf9\x01\xf7' + long_str)
|
|
313
|
+
|
|
314
|
+
# Encode a long vendor tlv attribute
|
|
315
|
+
first_avp = b'\x1a\x15\x00\x00\x00\x10\x03\x0f\x01\x07value\x02\x06\x00\x00\x00\x02'
|
|
316
|
+
second_avp = b'\x1a\xff\x00\x00\x00\x10\x03\xf9\x01\xf7' + long_str
|
|
317
|
+
self.assertEqual(
|
|
318
|
+
encode((16, 3), {1: [b'value', long_str], 2: [b'\x00\x00\x00\x02']}),
|
|
319
|
+
first_avp + second_avp)
|
|
320
|
+
|
|
321
|
+
def testPktEncodeAttributes(self):
|
|
322
|
+
self.packet[1] = [b'value']
|
|
323
|
+
self.assertEqual(self.packet._PktEncodeAttributes(),
|
|
324
|
+
b'\x01\x07value')
|
|
325
|
+
|
|
326
|
+
self.packet.clear()
|
|
327
|
+
self.packet[(16, 2)] = [b'value']
|
|
328
|
+
self.assertEqual(self.packet._PktEncodeAttributes(),
|
|
329
|
+
b'\x1a\x0d\x00\x00\x00\x10\x02\x07value')
|
|
330
|
+
|
|
331
|
+
self.packet.clear()
|
|
332
|
+
self.packet[1] = [b'one', b'two', b'three']
|
|
333
|
+
self.assertEqual(self.packet._PktEncodeAttributes(),
|
|
334
|
+
b'\x01\x05one\x01\x05two\x01\x07three')
|
|
335
|
+
|
|
336
|
+
self.packet.clear()
|
|
337
|
+
self.packet[1] = [b'value']
|
|
338
|
+
self.packet[(16, 2)] = [b'value']
|
|
339
|
+
self.assertEqual(
|
|
340
|
+
self.packet._PktEncodeAttributes(),
|
|
341
|
+
b'\x01\x07value\x1a\x0d\x00\x00\x00\x10\x02\x07value')
|
|
342
|
+
|
|
343
|
+
def testPktDecodeVendorAttribute(self):
|
|
344
|
+
decode = self.packet._PktDecodeVendorAttribute
|
|
345
|
+
|
|
346
|
+
# Non-RFC2865 recommended form
|
|
347
|
+
self.assertEqual(decode(b''), [(26, b'')])
|
|
348
|
+
self.assertEqual(decode(b'12345'), [(26, b'12345')])
|
|
349
|
+
|
|
350
|
+
# Almost RFC2865 recommended form: bad length value
|
|
351
|
+
self.assertEqual(
|
|
352
|
+
decode(b'\x00\x00\x00\x01\x02\x06value'),
|
|
353
|
+
[(26, b'\x00\x00\x00\x01\x02\x06value')])
|
|
354
|
+
|
|
355
|
+
# Proper RFC2865 recommended form
|
|
356
|
+
self.assertEqual(
|
|
357
|
+
decode(b'\x00\x00\x00\x10\x02\x07value'),
|
|
358
|
+
[((16, 2), b'value')])
|
|
359
|
+
|
|
360
|
+
def testPktDecodeTlvAttribute(self):
|
|
361
|
+
decode = self.packet._PktDecodeTlvAttribute
|
|
362
|
+
|
|
363
|
+
decode(4, b'\x01\x07value')
|
|
364
|
+
self.assertEqual(self.packet[4], {1: [b'value']})
|
|
365
|
+
|
|
366
|
+
# add another instance of the same sub attribute
|
|
367
|
+
decode(4, b'\x01\x07other')
|
|
368
|
+
self.assertEqual(self.packet[4], {1: [b'value', b'other']})
|
|
369
|
+
|
|
370
|
+
# add a different sub attribute
|
|
371
|
+
decode(4, b'\x02\x07\x00\x00\x00\x01')
|
|
372
|
+
self.assertEqual(self.packet[4], {
|
|
373
|
+
1: [b'value', b'other'],
|
|
374
|
+
2: [b'\x00\x00\x00\x01']
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
def testDecodePacketWithEmptyPacket(self):
|
|
378
|
+
try:
|
|
379
|
+
self.packet.DecodePacket(b'')
|
|
380
|
+
except packet.PacketError as e:
|
|
381
|
+
self.assertTrue('header is corrupt' in str(e))
|
|
382
|
+
else:
|
|
383
|
+
self.fail()
|
|
384
|
+
|
|
385
|
+
def testDecodePacketWithInvalidLength(self):
|
|
386
|
+
try:
|
|
387
|
+
self.packet.DecodePacket(b'\x00\x00\x00\x001234567890123456')
|
|
388
|
+
except packet.PacketError as e:
|
|
389
|
+
self.assertTrue('invalid length' in str(e))
|
|
390
|
+
else:
|
|
391
|
+
self.fail()
|
|
392
|
+
|
|
393
|
+
def testDecodePacketWithTooBigPacket(self):
|
|
394
|
+
try:
|
|
395
|
+
self.packet.DecodePacket(b'\x00\x00\x24\x00' + (0x2400 - 4) * b'X')
|
|
396
|
+
except packet.PacketError as e:
|
|
397
|
+
self.assertTrue('too long' in str(e))
|
|
398
|
+
else:
|
|
399
|
+
self.fail()
|
|
400
|
+
|
|
401
|
+
def testDecodePacketWithPartialAttributes(self):
|
|
402
|
+
try:
|
|
403
|
+
self.packet.DecodePacket(
|
|
404
|
+
b'\x01\x02\x00\x151234567890123456\x00')
|
|
405
|
+
except packet.PacketError as e:
|
|
406
|
+
self.assertTrue('header is corrupt' in str(e))
|
|
407
|
+
else:
|
|
408
|
+
self.fail()
|
|
409
|
+
|
|
410
|
+
def testDecodePacketWithoutAttributes(self):
|
|
411
|
+
self.packet.DecodePacket(b'\x01\x02\x00\x141234567890123456')
|
|
412
|
+
self.assertEqual(self.packet.code, 1)
|
|
413
|
+
self.assertEqual(self.packet.id, 2)
|
|
414
|
+
self.assertEqual(self.packet.authenticator, b'1234567890123456')
|
|
415
|
+
self.assertEqual(self.packet.keys(), [])
|
|
416
|
+
|
|
417
|
+
def testDecodePacketWithBadAttribute(self):
|
|
418
|
+
try:
|
|
419
|
+
self.packet.DecodePacket(
|
|
420
|
+
b'\x01\x02\x00\x161234567890123456\x00\x01')
|
|
421
|
+
except packet.PacketError as e:
|
|
422
|
+
self.assertTrue('too small' in str(e))
|
|
423
|
+
else:
|
|
424
|
+
self.fail()
|
|
425
|
+
|
|
426
|
+
def testDecodePacketWithEmptyAttribute(self):
|
|
427
|
+
self.packet.DecodePacket(
|
|
428
|
+
b'\x01\x02\x00\x161234567890123456\x01\x02')
|
|
429
|
+
self.assertEqual(self.packet[1], [b''])
|
|
430
|
+
|
|
431
|
+
def testDecodePacketWithAttribute(self):
|
|
432
|
+
self.packet.DecodePacket(
|
|
433
|
+
b'\x01\x02\x00\x1b1234567890123456\x01\x07value')
|
|
434
|
+
self.assertEqual(self.packet[1], [b'value'])
|
|
435
|
+
|
|
436
|
+
def testDecodePacketWithTlvAttribute(self):
|
|
437
|
+
self.packet.DecodePacket(
|
|
438
|
+
b'\x01\x02\x00\x1d1234567890123456\x04\x09\x01\x07value')
|
|
439
|
+
self.assertEqual(self.packet[4], {1: [b'value']})
|
|
440
|
+
|
|
441
|
+
def testDecodePacketWithVendorTlvAttribute(self):
|
|
442
|
+
self.packet.DecodePacket(
|
|
443
|
+
b'\x01\x02\x00\x231234567890123456\x1a\x0f\x00\x00\x00\x10\x03\x09\x01\x07value')
|
|
444
|
+
self.assertEqual(self.packet[(16, 3)], {1: [b'value']})
|
|
445
|
+
|
|
446
|
+
def testDecodePacketWithTlvAttributeWith2SubAttributes(self):
|
|
447
|
+
self.packet.DecodePacket(
|
|
448
|
+
b'\x01\x02\x00\x231234567890123456\x04\x0f\x01\x07value\x02\x06\x00\x00\x00\x09')
|
|
449
|
+
self.assertEqual(self.packet[4], {1: [b'value'], 2: [b'\x00\x00\x00\x09']})
|
|
450
|
+
|
|
451
|
+
def testDecodePacketWithSplitTlvAttribute(self):
|
|
452
|
+
self.packet.DecodePacket(
|
|
453
|
+
b'\x01\x02\x00\x251234567890123456\x04\x09\x01\x07value\x04\x09\x02\x06\x00\x00\x00\x09')
|
|
454
|
+
self.assertEqual(self.packet[4], {1: [b'value'], 2: [b'\x00\x00\x00\x09']})
|
|
455
|
+
|
|
456
|
+
def testDecodePacketWithMultiValuedAttribute(self):
|
|
457
|
+
self.packet.DecodePacket(
|
|
458
|
+
b'\x01\x02\x00\x1e1234567890123456\x01\x05one\x01\x05two')
|
|
459
|
+
self.assertEqual(self.packet[1], [b'one', b'two'])
|
|
460
|
+
|
|
461
|
+
def testDecodePacketWithTwoAttributes(self):
|
|
462
|
+
self.packet.DecodePacket(
|
|
463
|
+
b'\x01\x02\x00\x1e1234567890123456\x01\x05one\x01\x05two')
|
|
464
|
+
self.assertEqual(self.packet[1], [b'one', b'two'])
|
|
465
|
+
|
|
466
|
+
def testDecodePacketWithVendorAttribute(self):
|
|
467
|
+
self.packet.DecodePacket(
|
|
468
|
+
b'\x01\x02\x00\x1b1234567890123456\x1a\x07value')
|
|
469
|
+
self.assertEqual(self.packet[26], [b'value'])
|
|
470
|
+
|
|
471
|
+
def testEncodeKeyValues(self):
|
|
472
|
+
self.assertEqual(self.packet._EncodeKeyValues(1, '1234'), (1, '1234'))
|
|
473
|
+
|
|
474
|
+
def testEncodeKey(self):
|
|
475
|
+
self.assertEqual(self.packet._EncodeKey(1), 1)
|
|
476
|
+
|
|
477
|
+
def testAddAttribute(self):
|
|
478
|
+
self.packet.AddAttribute('Test-String', '1')
|
|
479
|
+
self.assertEqual(self.packet['Test-String'], ['1'])
|
|
480
|
+
self.packet.AddAttribute('Test-String', '1')
|
|
481
|
+
self.assertEqual(self.packet['Test-String'], ['1', '1'])
|
|
482
|
+
self.packet.AddAttribute('Test-String', ['2', '3'])
|
|
483
|
+
self.assertEqual(self.packet['Test-String'], ['1', '1', '2', '3'])
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class AuthPacketConstructionTests(PacketConstructionTests):
|
|
487
|
+
klass = packet.AuthPacket
|
|
488
|
+
|
|
489
|
+
def testConstructorDefaults(self):
|
|
490
|
+
pkt = self.klass()
|
|
491
|
+
self.assertEqual(pkt.code, packet.AccessRequest)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
class AuthPacketTests(unittest.TestCase):
|
|
495
|
+
def setUp(self):
|
|
496
|
+
self.path = os.path.join(home, 'data')
|
|
497
|
+
self.dict = Dictionary(os.path.join(self.path, 'full'))
|
|
498
|
+
self.packet = packet.AuthPacket(id=0, secret=b'secret',
|
|
499
|
+
authenticator=b'01234567890ABCDEF',
|
|
500
|
+
dict=self.dict)
|
|
501
|
+
|
|
502
|
+
def testCreateReply(self):
|
|
503
|
+
reply = self.packet.CreateReply(**{'Test-Integer': 10})
|
|
504
|
+
self.assertEqual(reply.code, packet.AccessAccept)
|
|
505
|
+
self.assertEqual(reply.id, self.packet.id)
|
|
506
|
+
self.assertEqual(reply.secret, self.packet.secret)
|
|
507
|
+
self.assertEqual(reply.authenticator, self.packet.authenticator)
|
|
508
|
+
self.assertEqual(reply['Test-Integer'], [10])
|
|
509
|
+
|
|
510
|
+
def testRequestPacket(self):
|
|
511
|
+
self.assertEqual(self.packet.RequestPacket(),
|
|
512
|
+
b'\x01\x00\x00\x1401234567890ABCDE')
|
|
513
|
+
|
|
514
|
+
def testRequestPacketCreatesAuthenticator(self):
|
|
515
|
+
self.packet.authenticator = None
|
|
516
|
+
self.packet.RequestPacket()
|
|
517
|
+
self.assertTrue(self.packet.authenticator is not None)
|
|
518
|
+
|
|
519
|
+
def testRequestPacketCreatesID(self):
|
|
520
|
+
self.packet.id = None
|
|
521
|
+
self.packet.RequestPacket()
|
|
522
|
+
self.assertTrue(self.packet.id is not None)
|
|
523
|
+
|
|
524
|
+
def testPwCryptEmptyPassword(self):
|
|
525
|
+
self.assertEqual(self.packet.PwCrypt(''), b'')
|
|
526
|
+
|
|
527
|
+
def testPwCryptPassword(self):
|
|
528
|
+
self.assertEqual(self.packet.PwCrypt('Simplon'),
|
|
529
|
+
b'\xd3U;\xb23\r\x11\xba\x07\xe3\xa8*\xa8x\x14\x01')
|
|
530
|
+
|
|
531
|
+
def testPwCryptSetsAuthenticator(self):
|
|
532
|
+
self.packet.authenticator = None
|
|
533
|
+
self.packet.PwCrypt('')
|
|
534
|
+
self.assertTrue(self.packet.authenticator is not None)
|
|
535
|
+
|
|
536
|
+
def testPwDecryptEmptyPassword(self):
|
|
537
|
+
self.assertEqual(self.packet.PwDecrypt(b''), '')
|
|
538
|
+
|
|
539
|
+
def testPwDecryptPassword(self):
|
|
540
|
+
self.assertEqual(self.packet.PwDecrypt(
|
|
541
|
+
b'\xd3U;\xb23\r\x11\xba\x07\xe3\xa8*\xa8x\x14\x01'),
|
|
542
|
+
'Simplon')
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
class AuthPacketChapTests(unittest.TestCase):
|
|
546
|
+
def setUp(self):
|
|
547
|
+
self.path = os.path.join(home, 'data')
|
|
548
|
+
self.dict = Dictionary(os.path.join(self.path, 'chap'))
|
|
549
|
+
# self.packet = packet.Packet(id=0, secret=b'secret',
|
|
550
|
+
# dict=self.dict)
|
|
551
|
+
self.client = Client(server='localhost', secret=b'secret',
|
|
552
|
+
dict=self.dict)
|
|
553
|
+
|
|
554
|
+
def testVerifyChapPasswd(self):
|
|
555
|
+
chap_id = b'9'
|
|
556
|
+
chap_challenge = b'987654321'
|
|
557
|
+
chap_password = chap_id + md5_constructor(
|
|
558
|
+
chap_id + b'test_password' + chap_challenge).digest()
|
|
559
|
+
pkt = self.client.CreateAuthPacket(
|
|
560
|
+
code=packet.AccessChallenge,
|
|
561
|
+
authenticator=b'ABCDEFG',
|
|
562
|
+
User_Name='test_name',
|
|
563
|
+
CHAP_Challenge=chap_challenge,
|
|
564
|
+
CHAP_Password=chap_password
|
|
565
|
+
)
|
|
566
|
+
self.assertEqual(pkt['CHAP-Challenge'][0], chap_challenge)
|
|
567
|
+
self.assertEqual(pkt['CHAP-Password'][0], chap_password)
|
|
568
|
+
self.assertEqual(pkt.VerifyChapPasswd('test_password'), True)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class AcctPacketConstructionTests(PacketConstructionTests):
|
|
572
|
+
klass = packet.AcctPacket
|
|
573
|
+
|
|
574
|
+
def testConstructorDefaults(self):
|
|
575
|
+
pkt = self.klass()
|
|
576
|
+
self.assertEqual(pkt.code, packet.AccountingRequest)
|
|
577
|
+
|
|
578
|
+
def testConstructorRawPacket(self):
|
|
579
|
+
raw = (b'\x00\x00\x00\x14\xb0\x5e\x4b\xfb\xcc\x1c'
|
|
580
|
+
b'\x8c\x8e\xc4\x72\xac\xea\x87\x45\x63\xa7')
|
|
581
|
+
pkt = self.klass(packet=raw)
|
|
582
|
+
self.assertEqual(pkt.raw_packet, raw)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class AcctPacketTests(unittest.TestCase):
|
|
586
|
+
def setUp(self):
|
|
587
|
+
self.path = os.path.join(home, 'data')
|
|
588
|
+
self.dict = Dictionary(os.path.join(self.path, 'full'))
|
|
589
|
+
self.packet = packet.AcctPacket(id=0, secret=b'secret',
|
|
590
|
+
authenticator=b'01234567890ABCDEF',
|
|
591
|
+
dict=self.dict)
|
|
592
|
+
|
|
593
|
+
def testCreateReply(self):
|
|
594
|
+
reply = self.packet.CreateReply(**{'Test-Integer': 10})
|
|
595
|
+
self.assertEqual(reply.code, packet.AccountingResponse)
|
|
596
|
+
self.assertEqual(reply.id, self.packet.id)
|
|
597
|
+
self.assertEqual(reply.secret, self.packet.secret)
|
|
598
|
+
self.assertEqual(reply.authenticator, self.packet.authenticator)
|
|
599
|
+
self.assertEqual(reply['Test-Integer'], [10])
|
|
600
|
+
|
|
601
|
+
def testVerifyAcctRequest(self):
|
|
602
|
+
rawpacket = self.packet.RequestPacket()
|
|
603
|
+
pkt = packet.AcctPacket(secret=b'secret', packet=rawpacket)
|
|
604
|
+
self.assertEqual(pkt.VerifyAcctRequest(), True)
|
|
605
|
+
|
|
606
|
+
pkt.secret = b'different'
|
|
607
|
+
self.assertEqual(pkt.VerifyAcctRequest(), False)
|
|
608
|
+
pkt.secret = b'secret'
|
|
609
|
+
|
|
610
|
+
pkt.raw_packet = b'X' + pkt.raw_packet[1:]
|
|
611
|
+
self.assertEqual(pkt.VerifyAcctRequest(), False)
|
|
612
|
+
|
|
613
|
+
def testRequestPacket(self):
|
|
614
|
+
self.assertEqual(self.packet.RequestPacket(),
|
|
615
|
+
b'\x04\x00\x00\x14\x95\xdf\x90\xccbn\xfb\x15G!\x13\xea\xfa>6\x0f')
|
|
616
|
+
|
|
617
|
+
def testRequestPacketSetsId(self):
|
|
618
|
+
self.packet.id = None
|
|
619
|
+
self.packet.RequestPacket()
|
|
620
|
+
self.assertTrue(self.packet.id is not None)
|
|
621
|
+
|
|
622
|
+
def testRealisticUnknownAttributes(self):
|
|
623
|
+
""" Test a realistic Accounting Packet from raw
|
|
624
|
+
User-Name: [u'user@example.com']
|
|
625
|
+
NAS-IP-Address: ['1.2.3.4']
|
|
626
|
+
Service-Type: ['Framed-User']
|
|
627
|
+
Framed-Protocol: ['NAS-Prompt-User']
|
|
628
|
+
Framed-IP-Address: ['1.2.3.4']
|
|
629
|
+
Acct-Status-Type: ['Interim-Update']
|
|
630
|
+
Acct-Delay-Time: [0]
|
|
631
|
+
Acct-Input-Octets: [1290826858]
|
|
632
|
+
Acct-Output-Octets: [3551101035]
|
|
633
|
+
Acct-Session-Id: [u'90dbd65a18b0a6c']
|
|
634
|
+
Acct-Authentic: ['RADIUS']
|
|
635
|
+
Acct-Session-Time: [769500]
|
|
636
|
+
Acct-Input-Packets: [7403861]
|
|
637
|
+
Acct-Output-Packets: [10928170]
|
|
638
|
+
Acct-Link-Count: [1]
|
|
639
|
+
Acct-Input-Gigawords: [0]
|
|
640
|
+
Acct-Output-Gigawords: [2]
|
|
641
|
+
Event-Timestamp: [1554155989]
|
|
642
|
+
# vendor specific
|
|
643
|
+
NAS-Port-Type: ['Virtual']
|
|
644
|
+
(26, 594, 1): [u'UNKNOWN_PRODUCT']
|
|
645
|
+
# implementation specific fields
|
|
646
|
+
224: ['24P\x10\x00\x22\x96\xc9']
|
|
647
|
+
228: ['\xfe\x99\xd0P']
|
|
648
|
+
"""
|
|
649
|
+
# path = os.path.join(home, 'tests', 'data')
|
|
650
|
+
path = os.path.join(home, 'data')
|
|
651
|
+
dictObj = Dictionary(os.path.join(path, 'realistic'))
|
|
652
|
+
raw = b'\x04\x8e\x00\xc4\xb2\xf8z\xdb\xac\xfd9l\x9dI?E\x8c%\xe9'\
|
|
653
|
+
b'\xf5\x01\x12user@example.com\x04\x06\x01\x02\x03\x04\x06\x06'\
|
|
654
|
+
b'\x00\x00\x00\x02\x07\x06\x00\x00\x00\x07\x08\x06\x01\x02\x03'\
|
|
655
|
+
b'\x04(\x06\x00\x00\x00\x03)\x06\x00\x00\x00\x00*\x06L\xf0tj+'\
|
|
656
|
+
b'\x06\xd3\xa9\x80k,\x1190dbd65a18b0a6c-\x06\x00\x00\x00\x01.'\
|
|
657
|
+
b'\x06\x00\x0b\xbd\xdc/\x06\x00p\xf9U0\x06\x00\xa6\xc0*3\x06'\
|
|
658
|
+
b'\x00\x00\x00\x014\x06\x00\x00\x00\x005\x06\x00\x00\x00\x027'\
|
|
659
|
+
b'\x06\\\xa2\x89\xd5=\x06\x00\x00\x00\x05\x1a\x17\x00\x00\x02R'\
|
|
660
|
+
b'\x01\x11UNKNOWN_PRODUCT\xe0\n24P\x10\x00\x22\x96\xc9\xe4\x06'\
|
|
661
|
+
b'\xfe\x99\xd0P'
|
|
662
|
+
|
|
663
|
+
pkt = packet.AcctPacket(dict=dictObj, packet=raw)
|
|
664
|
+
self.assertEqual(pkt.raw_packet, raw)
|
|
665
|
+
|
|
666
|
+
# Test verifies packet parses correctly
|
|
667
|
+
self.assertEqual(pkt.code, packet.AccountingRequest) # 4
|
|
668
|
+
self.assertEqual(pkt['User-Name'], ['user@example.com'])
|
|
669
|
+
self.assertEqual(pkt['NAS-IP-Address'], ['1.2.3.4'])
|
|
670
|
+
self.assertEqual(pkt['Acct-Status-Type'], ['Interim-Update'])
|
|
671
|
+
self.assertEqual(pkt['Acct-Session-Id'], ['90dbd65a18b0a6c'])
|
|
672
|
+
self.assertEqual(pkt['Acct-Authentic'], ['RADIUS'])
|
|
673
|
+
|
|
674
|
+
# Unknown attributes preserved
|
|
675
|
+
self.assertEqual(pkt[224][0], b'24P\x10\x00\x22\x96\xc9')
|
|
676
|
+
self.assertEqual(pkt[228][0], b'\xfe\x99\xd0P')
|
|
677
|
+
|
|
678
|
+
# Vendor unknown preserved
|
|
679
|
+
self.assertEqual(pkt[(594, 1)], [b'UNKNOWN_PRODUCT'])
|