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.
Files changed (51) hide show
  1. docs/Makefile +20 -0
  2. docs/make.bat +36 -0
  3. docs/source/_static/logo.png +0 -0
  4. docs/source/api/client.rst +10 -0
  5. docs/source/api/dictionary.rst +10 -0
  6. docs/source/api/host.rst +7 -0
  7. docs/source/api/packet.rst +48 -0
  8. docs/source/api/proxy.rst +7 -0
  9. docs/source/api/server.rst +13 -0
  10. docs/source/conf.py +158 -0
  11. docs/source/index.rst +75 -0
  12. example/acct.py +41 -0
  13. example/auth.py +37 -0
  14. example/auth_async.py +164 -0
  15. example/client-coa.py +61 -0
  16. example/coa.py +40 -0
  17. example/dictionary +405 -0
  18. example/dictionary.freeradius +91 -0
  19. example/pyrad.log +0 -0
  20. example/server.py +68 -0
  21. example/server_async.py +117 -0
  22. example/status.py +26 -0
  23. pyrad/__init__.py +3 -3
  24. pyrad/client.py +14 -6
  25. pyrad/client_async.py +16 -13
  26. pyrad/dictfile.py +2 -5
  27. pyrad/dictionary.py +6 -7
  28. pyrad/host.py +1 -1
  29. pyrad/packet.py +145 -114
  30. pyrad/proxy.py +2 -2
  31. pyrad/server.py +3 -7
  32. pyrad/server_async.py +3 -4
  33. pyrad/tests/__init__.py +5 -0
  34. pyrad/tests/mock.py +145 -0
  35. pyrad/tests/test_bidict.py +56 -0
  36. pyrad/tests/test_client.py +183 -0
  37. pyrad/tests/test_dictionary.py +357 -0
  38. pyrad/tests/test_host.py +87 -0
  39. pyrad/tests/test_packet.py +679 -0
  40. pyrad/tests/test_proxy.py +96 -0
  41. pyrad/tests/test_server.py +323 -0
  42. pyrad/tests/test_tools.py +126 -0
  43. pyrad/tools.py +254 -158
  44. {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info}/METADATA +45 -22
  45. pyrad-2.5.1.dist-info/RECORD +51 -0
  46. {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info}/WHEEL +1 -1
  47. {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info/licenses}/LICENSE.txt +1 -1
  48. pyrad-2.5.1.dist-info/top_level.txt +3 -0
  49. pyrad-2.4.dist-info/RECORD +0 -19
  50. pyrad-2.4.dist-info/top_level.txt +0 -1
  51. {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info}/zip-safe +0 -0
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/python
2
+
3
+ import asyncio
4
+
5
+ import logging
6
+ import traceback
7
+ from pyrad.dictionary import Dictionary
8
+ from pyrad.server_async import ServerAsync
9
+ from pyrad.packet import AccessAccept
10
+ from pyrad.server import RemoteHost
11
+
12
+ try:
13
+ import uvloop
14
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
15
+ except:
16
+ pass
17
+
18
+ logging.basicConfig(level="DEBUG",
19
+ format="%(asctime)s [%(levelname)-8s] %(message)s")
20
+
21
+ class FakeServer(ServerAsync):
22
+
23
+ def __init__(self, loop, dictionary):
24
+
25
+ ServerAsync.__init__(self, loop=loop, dictionary=dictionary,
26
+ enable_pkt_verify=True, debug=True)
27
+
28
+
29
+ def handle_auth_packet(self, protocol, pkt, addr):
30
+
31
+ print("Received an authentication request with id ", pkt.id)
32
+ print('Authenticator ', pkt.authenticator.hex())
33
+ print('Secret ', pkt.secret)
34
+ print("Attributes: ")
35
+ for attr in pkt.keys():
36
+ print("%s: %s" % (attr, pkt[attr]))
37
+
38
+ reply = self.CreateReplyPacket(pkt, **{
39
+ "Service-Type": "Framed-User",
40
+ "Framed-IP-Address": '192.168.0.1',
41
+ "Framed-IPv6-Prefix": "fc66::1/64"
42
+ })
43
+
44
+ reply.code = AccessAccept
45
+ protocol.send_response(reply, addr)
46
+
47
+ def handle_acct_packet(self, protocol, pkt, addr):
48
+
49
+ print("Received an accounting request")
50
+ print("Attributes: ")
51
+ for attr in pkt.keys():
52
+ print("%s: %s" % (attr, pkt[attr]))
53
+
54
+ reply = self.CreateReplyPacket(pkt)
55
+ protocol.send_response(reply, addr)
56
+
57
+ def handle_coa_packet(self, protocol, pkt, addr):
58
+
59
+ print("Received an coa request")
60
+ print("Attributes: ")
61
+ for attr in pkt.keys():
62
+ print("%s: %s" % (attr, pkt[attr]))
63
+
64
+ reply = self.CreateReplyPacket(pkt)
65
+ protocol.send_response(reply, addr)
66
+
67
+ def handle_disconnect_packet(self, protocol, pkt, addr):
68
+
69
+ print("Received an disconnect request")
70
+ print("Attributes: ")
71
+ for attr in pkt.keys():
72
+ print("%s: %s" % (attr, pkt[attr]))
73
+
74
+ reply = self.CreateReplyPacket(pkt)
75
+ # COA NAK
76
+ reply.code = 45
77
+ protocol.send_response(reply, addr)
78
+
79
+
80
+ if __name__ == '__main__':
81
+
82
+ # create server and read dictionary
83
+ loop = asyncio.get_event_loop()
84
+ server = FakeServer(loop=loop, dictionary=Dictionary('dictionary'))
85
+
86
+ # add clients (address, secret, name)
87
+ server.hosts["127.0.0.1"] = RemoteHost("127.0.0.1",
88
+ b"Kah3choteereethiejeimaeziecumi",
89
+ "localhost")
90
+
91
+ try:
92
+
93
+ # Initialize transports
94
+ loop.run_until_complete(
95
+ asyncio.ensure_future(
96
+ server.initialize_transports(enable_auth=True,
97
+ enable_acct=True,
98
+ enable_coa=True)))
99
+
100
+ try:
101
+ # start server
102
+ loop.run_forever()
103
+ except KeyboardInterrupt as k:
104
+ pass
105
+
106
+ # Close transports
107
+ loop.run_until_complete(asyncio.ensure_future(
108
+ server.deinitialize_transports()))
109
+
110
+ except Exception as exc:
111
+ print('Error: ', exc)
112
+ print('\n'.join(traceback.format_exc().splitlines()))
113
+ # Close transports
114
+ loop.run_until_complete(asyncio.ensure_future(
115
+ server.deinitialize_transports()))
116
+
117
+ loop.close()
example/status.py ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/python
2
+ from pyrad.client import Client
3
+ from pyrad.dictionary import Dictionary
4
+ import socket
5
+ import sys
6
+ import pyrad.packet
7
+
8
+ srv = Client(server="localhost", authport=18121, secret=b"test", dict=Dictionary("dictionary"))
9
+
10
+ req = srv.CreateAuthPacket(code=pyrad.packet.StatusServer)
11
+ req["FreeRADIUS-Statistics-Type"] = "All"
12
+ req.add_message_authenticator()
13
+
14
+ try:
15
+ print("Sending FreeRADIUS status request")
16
+ reply = srv.SendPacket(req)
17
+ except pyrad.client.Timeout:
18
+ print("RADIUS server does not reply")
19
+ sys.exit(1)
20
+ except socket.error as error:
21
+ print("Network error: " + error[1])
22
+ sys.exit(1)
23
+
24
+ print("Attributes returned by server:")
25
+ for i in reply.keys():
26
+ print("%s: %s" % (i, reply[i]))
pyrad/__init__.py CHANGED
@@ -38,9 +38,9 @@ This package contains four modules:
38
38
 
39
39
  __docformat__ = 'epytext en'
40
40
 
41
- __author__ = 'Christian Giese <developer@gicnet.de>'
41
+ __author__ = 'Christian Giese <gic@gicnet.de>, Istvan Ruzman <istvan@ruzman.eu> and Stefan Lieberth <stefan@lieberth.net>'
42
42
  __url__ = 'http://pyrad.readthedocs.io/en/latest/?badge=latest'
43
- __copyright__ = 'Copyright 2002-2020 Wichert Akkerman and Christian Giese. All rights reserved.'
44
- __version__ = '2.4'
43
+ __copyright__ = 'Copyright 2002-2026 Wichert Akkerman, Christian Giese, Istvan Ruzman and Stefan Lieberth. All rights reserved.'
44
+ __version__ = '2.5.1'
45
45
 
46
46
  __all__ = ['client', 'dictionary', 'packet', 'server', 'tools', 'dictfile']
pyrad/client.py CHANGED
@@ -8,8 +8,8 @@ import hashlib
8
8
  import select
9
9
  import socket
10
10
  import time
11
- import six
12
11
  import struct
12
+ import six
13
13
  from pyrad import host
14
14
  from pyrad import packet
15
15
 
@@ -17,6 +17,7 @@ EAP_CODE_REQUEST = 1
17
17
  EAP_CODE_RESPONSE = 2
18
18
  EAP_TYPE_IDENTITY = 1
19
19
 
20
+
20
21
  class Timeout(Exception):
21
22
  """Simple exception class which is raised when a timeout occurs
22
23
  while waiting for a RADIUS server to respond."""
@@ -34,8 +35,7 @@ class Client(host.Host):
34
35
  :type timeout: float
35
36
  """
36
37
  def __init__(self, server, authport=1812, acctport=1813,
37
- coaport=3799, secret=six.b(''), dict=None, retries=3, timeout=5):
38
-
38
+ coaport=3799, secret=six.b(''), dict=None, retries=3, timeout=5, enforce_ma=False):
39
39
  """Constructor.
40
40
 
41
41
  :param server: hostname or IP address of RADIUS server
@@ -50,6 +50,8 @@ class Client(host.Host):
50
50
  :type secret: string
51
51
  :param dict: RADIUS dictionary
52
52
  :type dict: pyrad.dictionary.Dictionary
53
+ :param enforce_ma: Enforce usage and check of Message-Authenticator
54
+ :type enforce_ma: boolean
53
55
  """
54
56
  host.Host.__init__(self, authport, acctport, coaport, dict)
55
57
 
@@ -58,6 +60,7 @@ class Client(host.Host):
58
60
  self._socket = None
59
61
  self.retries = retries
60
62
  self.timeout = timeout
63
+ self.enforce_ma = enforce_ma
61
64
  self._poll = select.poll()
62
65
 
63
66
  def bind(self, addr):
@@ -74,12 +77,12 @@ class Client(host.Host):
74
77
 
75
78
  def _SocketOpen(self):
76
79
  try:
77
- family = socket.getaddrinfo(self.server, 'www')[0][0]
78
- except:
80
+ family = socket.getaddrinfo(self.server, 80)[0][0]
81
+ except Exception:
79
82
  family = socket.AF_INET
80
83
  if not self._socket:
81
84
  self._socket = socket.socket(family,
82
- socket.SOCK_DGRAM)
85
+ socket.SOCK_DGRAM)
83
86
  self._socket.setsockopt(socket.SOL_SOCKET,
84
87
  socket.SO_REUSEADDR, 1)
85
88
  self._poll.register(self._socket, select.POLLIN)
@@ -100,6 +103,9 @@ class Client(host.Host):
100
103
  :return: a new empty packet instance
101
104
  :rtype: pyrad.packet.AuthPacket
102
105
  """
106
+ if self.enforce_ma:
107
+ return host.Host.CreateAuthPacket(self, secret=self.secret,
108
+ message_authenticator=True, **args)
103
109
  return host.Host.CreateAuthPacket(self, secret=self.secret, **args)
104
110
 
105
111
  def CreateAcctPacket(self, **args):
@@ -164,6 +170,8 @@ class Client(host.Host):
164
170
  try:
165
171
  reply = pkt.CreateReply(packet=rawreply)
166
172
  if pkt.VerifyReply(reply, rawreply):
173
+ if hasattr(pkt, 'authenticator'):
174
+ reply.request_authenticator = pkt.authenticator
167
175
  return reply
168
176
  except packet.PacketError:
169
177
  pass
pyrad/client_async.py CHANGED
@@ -6,7 +6,6 @@ __docformat__ = "epytext en"
6
6
 
7
7
  from datetime import datetime
8
8
  import asyncio
9
- import six
10
9
  import logging
11
10
  import random
12
11
 
@@ -92,12 +91,10 @@ class DatagramProtocolClient(asyncio.Protocol):
92
91
  def connection_made(self, transport):
93
92
  self.transport = transport
94
93
  socket = transport.get_extra_info('socket')
95
- self.logger.info(
96
- '[%s:%d] Transport created with binding in %s:%d',
97
- self.server, self.port,
98
- socket.getsockname()[0],
99
- socket.getsockname()[1]
100
- )
94
+ self.logger.info('[%s:%d] Transport created with binding in %s:%d',
95
+ self.server, self.port,
96
+ socket.getsockname()[0],
97
+ socket.getsockname()[1])
101
98
 
102
99
  pre_loop = asyncio.get_event_loop()
103
100
  asyncio.set_event_loop(loop=self.client.loop)
@@ -121,19 +118,19 @@ class DatagramProtocolClient(asyncio.Protocol):
121
118
  try:
122
119
  reply = Packet(packet=data, dict=self.client.dict)
123
120
 
124
- if reply and reply.id in self.pending_requests:
121
+ if reply.code and reply.id in self.pending_requests:
125
122
  req = self.pending_requests[reply.id]
126
123
  packet = req['packet']
127
124
 
128
125
  reply.dict = packet.dict
129
126
  reply.secret = packet.secret
130
127
 
131
- if packet.VerifyReply(reply, data):
128
+ if packet.VerifyReply(reply, data, enforce_ma=self.client.enforce_ma):
132
129
  req['future'].set_result(reply)
133
130
  # Remove request for map
134
131
  del self.pending_requests[reply.id]
135
132
  else:
136
- self.logger.warn('[%s:%d] Ignore invalid reply for id %d. %s', self.server, self.port, reply.id)
133
+ self.logger.warn('[%s:%d] Ignore invalid reply for id %d: %s', self.server, self.port, reply.id, data)
137
134
  else:
138
135
  self.logger.warn('[%s:%d] Ignore invalid reply: %s', self.server, self.port, data)
139
136
 
@@ -175,9 +172,9 @@ class ClientAsync:
175
172
  """
176
173
  # noinspection PyShadowingBuiltins
177
174
  def __init__(self, server, auth_port=1812, acct_port=1813,
178
- coa_port=3799, secret=six.b(''), dict=None,
175
+ coa_port=3799, secret=b'', dict=None,
179
176
  loop=None, retries=3, timeout=30,
180
- logger_name='pyrad'):
177
+ logger_name='pyrad', enforce_ma=False):
181
178
 
182
179
  """Constructor.
183
180
 
@@ -216,6 +213,7 @@ class ClientAsync:
216
213
 
217
214
  self.protocol_coa = None
218
215
  self.coa_port = coa_port
216
+ self.enforce_ma = enforce_ma
219
217
 
220
218
  async def initialize_transports(self, enable_acct=False,
221
219
  enable_auth=False, enable_coa=False,
@@ -325,6 +323,11 @@ class ClientAsync:
325
323
  """
326
324
  if not self.protocol_auth:
327
325
  raise Exception('Transport not initialized')
326
+ if self.enforce_ma:
327
+ return AuthPacket(dict=self.dict,
328
+ id=self.protocol_auth.create_id(),
329
+ secret=self.secret,
330
+ message_authenticator=True, **args)
328
331
 
329
332
  return AuthPacket(dict=self.dict,
330
333
  id=self.protocol_auth.create_id(),
@@ -360,7 +363,7 @@ class ClientAsync:
360
363
  :rtype: pyrad.packet.Packet
361
364
  """
362
365
 
363
- if not self.protocol_acct:
366
+ if not self.protocol_coa:
364
367
  raise Exception('Transport not initialized')
365
368
 
366
369
  return CoAPacket(id=self.protocol_coa.create_id(),
pyrad/dictfile.py CHANGED
@@ -9,7 +9,6 @@ RADIUS $INCLUDE directives behind the scene.
9
9
  """
10
10
 
11
11
  import os
12
- import six
13
12
 
14
13
 
15
14
  class _Node(object):
@@ -54,10 +53,8 @@ class DictFile(object):
54
53
  self.__ReadNode(fil)
55
54
 
56
55
  def __ReadNode(self, fil):
57
- node = None
58
56
  parentdir = self.__CurDir()
59
- if isinstance(fil, six.string_types):
60
- fname = None
57
+ if isinstance(fil, str):
61
58
  if os.path.isabs(fil):
62
59
  fname = fil
63
60
  else:
@@ -105,7 +102,7 @@ class DictFile(object):
105
102
  def __next__(self):
106
103
  while self.stack:
107
104
  line = self.stack[-1].Next()
108
- if line == None:
105
+ if line is None:
109
106
  self.stack.pop()
110
107
  else:
111
108
  inc = self.__GetInclude(line)
pyrad/dictionary.py CHANGED
@@ -75,7 +75,6 @@ from pyrad import bidict
75
75
  from pyrad import tools
76
76
  from pyrad import dictfile
77
77
  from copy import copy
78
- import logging
79
78
 
80
79
  __docformat__ = 'epytext en'
81
80
 
@@ -224,12 +223,12 @@ class Dictionary(object):
224
223
  # Codes can be sent as hex, or octal or decimal string representations.
225
224
  tmp = []
226
225
  for c in codes:
227
- if c.startswith('0x'):
228
- tmp.append(int(c, 16))
229
- elif c.startswith('0o'):
230
- tmp.append(int(c, 8))
231
- else:
232
- tmp.append(int(c, 10))
226
+ if c.startswith('0x'):
227
+ tmp.append(int(c, 16))
228
+ elif c.startswith('0o'):
229
+ tmp.append(int(c, 8))
230
+ else:
231
+ tmp.append(int(c, 10))
233
232
  codes = tmp
234
233
 
235
234
  is_sub_attribute = (len(codes) > 1)
pyrad/host.py CHANGED
@@ -57,7 +57,7 @@ class Host(object):
57
57
 
58
58
  def CreateAcctPacket(self, **args):
59
59
  """Create a new accounting RADIUS packet.
60
- This utility function creates a new accouting RADIUS packet
60
+ This utility function creates a new accounting RADIUS packet
61
61
  which can be used to communicate with the RADIUS server this
62
62
  client talks to. This is initializing the new packet with the
63
63
  dictionary and secret used for the client.