New Upstream Release - pyrad
Ready changes
Summary
Merged new upstream version: 2.4 (was: 2.1).
Resulting package
Built on 2023-06-20T19:07 (took 4m41s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python3-pyrad
Lintian Result
Diff
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index ac72d1c..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-build/
-dist/
-docs/.build/
-pyrad.egg-info/
-*.pyc
-*__pycache__
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 23d81c1..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-language: python
-python:
- - "2.7"
- - "3.5"
-# command to install dependencies
-install: "pip install six nose coverage netaddr"
-# command to run tests
-script: nosetests -v --with-coverage
diff --git a/CHANGES.rst b/CHANGES.rst
deleted file mode 100644
index a98a190..0000000
--- a/CHANGES.rst
+++ /dev/null
@@ -1,231 +0,0 @@
-Changelog
-=========
-
-2.1 - Feb 2, 2017
------------------
-
-* Add CoA support (client and server)
-
-* Add tagged attribute support (send only).
-
-* Add salt encryption support (encrypt 2).
-
-* Add ascend data filter support (human readable format to octets).
-
-* Add ipv6 address and prefix support.
-
-* Add support for octet strings in hex (starting with 0x).
-
-* Add support for types short, signed and byte.
-
-* Add support for VSA's with multiple sub TLV's.
-
-* Use a different random generator to improve the security of generated
- packet ids and authenticators.
-
-
-2.0 - May 15, 2011
-------------------
-
-* Start moving codebase to PEP8 compatible coding style.
-
-* Add support for Python 3.2.
-
-* Several code cleanups. As a side effect Python versions before 2.6
- are unfortunatley no longer supported. If you use Python 2.5 or older
- Pyrad 1.2 will still work for you.
-
-
-1.2 - July 12, 2009
--------------------
-
-* Setup sphinx based documentation.
-
-* Use hashlib instead of md5, if present. This fixes deprecation warnings
- for python 2.6. Patch from Jeremy Liané.
-
-* Support parsing VENDOR format specifications in dictionary files. Patch by
- Kristoffer Grönlun.
-
-* Supprt $INCLUDE directores in dictionary files. Patch by
- Kristoffer Grönlun.
-
-* Standardize on 4 spaces for indents. Patch by Kristoffer Grönlund/
- Purplescout.
-
-* Make sure all encoding utility methods raise a TypeError if a value of
- the wrong type is passed in.
-
-
-1.1 - September 30, 2007
-------------------------
-
-* Add the 'octets' datatype from FreeRADIUS. This is treated just like string;
- the only difference is how FreeRADIUS prints it.
-
-* Check against unimplemented datatypes in EncodeData and DecodeData instead
- of assuming an identity transform works.
-
-* Make Packet.has_key and __contains__ gracefully handle unknown attributes.
- Based on a patch from Alexey V Michurun <am@rol.ru>.
-
-* Add a __delitem__ implementation to Packet. Based on a patch from
- Alexey V Michurun <am@rol.ru>.
-
-
-1.0 - September 16, 2007
-------------------------
-
-* Add unit tests. Pyrad now has 100% test coverage!
-
-* Moved the proxy server has been out of the server module to a new
- proxy module.
-
-* Fix several errors that prevented the proxy code from working.
-
-* Use the standard logging module instead of printing to stdout.
-
-* The default dictionary for Server instances was shared between all
- instances, possibly leading to unwanted data pollution. Each Server now
- gets its own dict instance if none is passed in to the constructor.
-
-* Fixed a timeout handling problem in the client: after receiving an
- invalid reply the current time was not updated, possibly leading to
- the client blocking forever.
-
-* Switch to setuptools, allowing pyrad to be distributed as an egg
- via the python package index.
-
-* Use absolute instead of relative imports.
-
-* Sockets are now opened with SO_REUSEADDR enabled to allow for faster
- restarts.
-
-
-0.9 - April 25, 2007
-------------------------
-
-* Start using trac to manage the project: http://code.wiggy.net/tracker/pyrad/
-
-* [bug 3] Fix handling of packets with an id of 0
-
-* [bug 2] Fix handling of file descriptor parameters in the server
- code and example.
-
-* [bug 4] Fix wrong variable name in exception raised when encountering
- an overly long packet.
-
-* [bug 5] Fix error message in parse error for dictionaries.
-
-* [bug 8] Packet.CreateAuthenticator is now a static method.
-
-
-0.8
----
-
-* Fix time-handling in the client packet sending code: it would loop
- forever since the now time was updated at the wrong moment. Fix from
- Michael Mitchell <Michael.Mitchell@team.telstra.com>
-
-* Fix passing of dict parameter when creating reply packets
-
-
-0.7
----
-
-* add HandleAuthPacket and HandleAcctPacket hooks to Server class.
- Request from Thomas Boettcher.
-
-* Pass on dict attribute when creating a reply packet. Requested by
- Thomas Boettcher.
-
-* Allow specififying new attributes when using
- Server.CreateReplyPacket. Requested by Thomas Boettcher.
-
-
-0.6
----
-
-* packet.VerifyReply() had a syntax error when not called with a raw packet.
-
-* Add bind() method to the Client class.
-
-* [SECURITY] Fix handling of timeouts in client module: when a bad
- packet was received pyrad immediately started the next retry instead of
- discarding it and waiting for a timeout. This could be exploited by
- sending a number of bogus responses before a correct reply to make pyrad
- not see the real response.
-
-* correctly set Acct-Delay-Time when resending accounting requests packets.
-
-* verify account request packages as well (from Farshad Khoshkhui).
-
-* protect against packets with bogus lengths (from Farshad Khoshkhui).
-
-
-0.5
----
-
-* Fix typo in server class which broke handling of accounting packets.
-
-* Create seperate AuthPacket and AcctPacket classes; this resulted in
- a fair number of API changes.
-
-* Packets now know how to create and verify replies.
-
-* Client now directs authentication and accounting packets to the
- correct port on the server.
-
-* Add twisted support via the new curved module.
-
-* Fix incorrect exception handling in client code.
-
-* Update example server to handle accounting packets.
-
-* Add example for sending account packets.
-
-
-0.4
----
-
-* Fix last case of bogus exception usage.
-
-* Move RADIUS code constants to packet module.
-
-* Add support for decoding passwords and generating reply packets to Packet
- class.
-
-* Add basic RADIUS server and proxy implementation.
-
-
-0.3
----
-
-* client.Timeout is now derived from Exception.
-
-* Docstring documentation added.
-
-* Include example dictionaries and authentication script.
-
-
-0.2
----
-
-* Use proper exceptions.
-
-* Encode and decode vendor attributes.
-
-* Dictionary can parse vendor dictionaries.
-
-* Dictionary can handle attribute values.
-
-* Enhance most constructors; they now take extra optional parameters
- with initialisation info.
-
-* No longer use obsolete python interfaces like whrandom.
-
-
-0.1
----
-
-* First release
diff --git a/LICENSE.txt b/LICENSE.txt
deleted file mode 100644
index e01f563..0000000
--- a/LICENSE.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-Copyright 2002-2008 Wichert Akkerman. All rights reserved.
-Copyright 2007-2008 Simplon. All rights reserved.
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-3. Neither the name of the University nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
-
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 6cb73b5..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-recursive-include example *
-prune example/.svn
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..155c290
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,98 @@
+Metadata-Version: 1.1
+Name: pyrad
+Version: 2.4
+Summary: RADIUS tools
+Home-page: https://github.com/pyradius/pyrad
+Author: Istvan Ruzman, Christian Giese
+Author-email: istvan@ruzman.eu, developer@gicnet.de
+License: BSD
+Description: .. image:: https://travis-ci.org/pyradius/pyrad.svg?branch=master
+ :target: https://travis-ci.org/pyradius/pyrad
+ .. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
+ :target: https://coveralls.io/github/pyradius/pyrad?branch=master
+ .. image:: https://img.shields.io/pypi/v/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+ .. image:: https://img.shields.io/pypi/pyversions/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+ .. image:: https://img.shields.io/pypi/dm/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+ .. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
+ :target: http://pyrad.readthedocs.io/en/latest/?badge=latest
+ :alt: Documentation Status
+ .. image:: https://img.shields.io/pypi/l/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+
+ Introduction
+ ============
+
+ pyrad is an implementation of a RADIUS client/server as described in RFC2865.
+ It takes care of all the details like building RADIUS packets, sending
+ them and decoding responses.
+
+ Here is an example of doing a authentication request::
+
+ from __future__ import print_function
+ from pyrad.client import Client
+ from pyrad.dictionary import Dictionary
+ import pyrad.packet
+
+ srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi",
+ dict=Dictionary("dictionary"))
+
+ # create request
+ req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
+ User_Name="wichert", NAS_Identifier="localhost")
+ req["User-Password"] = req.PwCrypt("password")
+
+ # send request
+ reply = srv.SendPacket(req)
+
+ if reply.code == pyrad.packet.AccessAccept:
+ print("access accepted")
+ else:
+ print("access denied")
+
+ print("Attributes returned by server:")
+ for i in reply.keys():
+ print("%s: %s" % (i, reply[i]))
+
+
+
+ Requirements & Installation
+ ===========================
+
+ pyrad requires Python 2.7, or Python 3.6 or later
+
+ Installing is simple; pyrad uses the standard distutils system for installing
+ Python modules::
+
+ python setup.py install
+
+
+ Author, Copyright, Availability
+ ===============================
+
+ pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by
+ Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
+
+ This project is licensed under a BSD license.
+
+ Copyright and license information can be found in the LICENSE.txt file.
+
+ The current version and documentation can be found on pypi:
+ https://pypi.org/project/pyrad/
+
+ Bugs and wishes can be submitted in the pyrad issue tracker on github:
+ https://github.com/pyradius/pyrad/issues
+
+Keywords: radius,authentication
+Platform: UNKNOWN
+Classifier: Development Status :: 6 - Mature
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
diff --git a/README.rst b/README.rst
index e530f57..5f85783 100644
--- a/README.rst
+++ b/README.rst
@@ -1,12 +1,18 @@
-.. image:: https://travis-ci.org/wichert/pyrad.svg?branch=master
- :target: https://travis-ci.org/wichert/pyrad
+.. image:: https://travis-ci.org/pyradius/pyrad.svg?branch=master
+ :target: https://travis-ci.org/pyradius/pyrad
+.. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
+ :target: https://coveralls.io/github/pyradius/pyrad?branch=master
.. image:: https://img.shields.io/pypi/v/pyrad.svg
:target: https://pypi.python.org/pypi/pyrad
+.. image:: https://img.shields.io/pypi/pyversions/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
.. image:: https://img.shields.io/pypi/dm/pyrad.svg
:target: https://pypi.python.org/pypi/pyrad
.. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
:target: http://pyrad.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
+.. image:: https://img.shields.io/pypi/l/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
Introduction
============
@@ -47,7 +53,7 @@ Here is an example of doing a authentication request::
Requirements & Installation
===========================
-pyrad requires Python 2.6 or later, or Python 3.2 or later
+pyrad requires Python 2.7, or Python 3.6 or later
Installing is simple; pyrad uses the standard distutils system for installing
Python modules::
@@ -58,13 +64,15 @@ Python modules::
Author, Copyright, Availability
===============================
-pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is licensed
-under a BSD license.
+pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by
+Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
+
+This project is licensed under a BSD license.
Copyright and license information can be found in the LICENSE.txt file.
The current version and documentation can be found on pypi:
-http://pypi.python.org/pypi/pyrad
+https://pypi.org/project/pyrad/
Bugs and wishes can be submitted in the pyrad issue tracker on github:
-https://github.com/wichert/pyrad/issues
+https://github.com/pyradius/pyrad/issues
diff --git a/TODO.rst b/TODO.rst
deleted file mode 100644
index 165b971..0000000
--- a/TODO.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-ToDo
-====
diff --git a/debian/changelog b/debian/changelog
index fa7ea5c..e855e25 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+pyrad (2.4-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Tue, 20 Jun 2023 19:03:13 -0000
+
pyrad (2.1-3) unstable; urgency=low
[ Debian Janitor ]
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 1446ca6..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-SPHINXPROJ = pyrad
-SOURCEDIR = source
-BUILDDIR = build
-
-# Put it first so that "make" without argument is like "make help".
-help:
- @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index 9fa49d9..0000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,36 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-set SPHINXPROJ=pyrad
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.http://sphinx-doc.org/
- exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
diff --git a/docs/source/_static/logo.png b/docs/source/_static/logo.png
deleted file mode 100644
index a4e16d5..0000000
Binary files a/docs/source/_static/logo.png and /dev/null differ
diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst
deleted file mode 100644
index ae90c0d..0000000
--- a/docs/source/api/client.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-:mod:`pyrad.client` -- basic client
-===================================
-
-.. automodule:: pyrad.client
-
- .. autoclass:: Timeout
- :members:
-
- .. autoclass:: Client
- :members:
diff --git a/docs/source/api/dictionary.rst b/docs/source/api/dictionary.rst
deleted file mode 100644
index da370e8..0000000
--- a/docs/source/api/dictionary.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-:mod:`pyrad.dictionary` -- RADIUS dictionary
-============================================
-
-.. automodule:: pyrad.dictionary
-
- .. autoclass:: ParseError
- :members:
-
- .. autoclass:: Dictionary
- :members:
diff --git a/docs/source/api/host.rst b/docs/source/api/host.rst
deleted file mode 100644
index 29e1760..0000000
--- a/docs/source/api/host.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-:mod:`pyrad.host` -- RADIUS host definition
-===========================================
-
-.. automodule:: pyrad.host
-
- .. autoclass:: Host
- :members:
diff --git a/docs/source/api/packet.rst b/docs/source/api/packet.rst
deleted file mode 100644
index 05c4a21..0000000
--- a/docs/source/api/packet.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-:mod:`pyrad.packet` -- packet encoding and decoding
-===================================================
-
-.. automodule:: pyrad.packet
-
- .. autoclass:: Packet
- :members:
-
- .. autoclass:: AuthPacket
- :members:
-
- .. autoclass:: AcctPacket
- :members:
-
- .. autoclass:: CoAPacket
- :members:
-
- .. autoclass:: PacketError
- :members:
-
-
-Constants
----------
-
-The :mod:`pyrad.packet` module defines several common constants
-that are useful when dealing with RADIUS packets.
-
-The following packet codes are defined:
-
-================== ======
-Constant name Value
-================== ======
-AccessRequest 1
------------------- ------
-AccessAccept 2
-AccessReject 3
-AccountingRequest 4
-AccountingResponse 5
-AccessChallenge 11
-StatusServer 12
-StatusClient 13
-DisconnectRequest 40
-DisconnectACK 41
-DisconnectNAK 42
-CoARequest 43
-CoAACK 44
-CoANAK 45
-================== ======
diff --git a/docs/source/api/proxy.rst b/docs/source/api/proxy.rst
deleted file mode 100644
index 34017a4..0000000
--- a/docs/source/api/proxy.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-:mod:`pyrad.proxy` -- basic proxy
-=================================
-
-.. automodule:: pyrad.proxy
-
- .. autoclass:: Proxy
- :members:
diff --git a/docs/source/api/server.rst b/docs/source/api/server.rst
deleted file mode 100644
index 2cfbd38..0000000
--- a/docs/source/api/server.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-:mod:`pyrad.server` -- basic server
-===================================
-
-.. automodule:: pyrad.server
-
- .. autoclass:: RemoteHost
- :members:
-
- .. autoclass:: ServerPacketError
- :members:
-
- .. autoclass:: Server
- :members:
diff --git a/docs/source/conf.py b/docs/source/conf.py
deleted file mode 100644
index 3cf1b40..0000000
--- a/docs/source/conf.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# pyrad documentation build configuration file, created by
-# sphinx-quickstart on Thu Feb 2 15:16:16 2017.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-
-import os
-import sys
-sys.path.insert(0, os.path.abspath('../../'))
-
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#
-# needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = ['sphinx.ext.autodoc',
- 'sphinx.ext.intersphinx',
- 'sphinx.ext.todo',
- 'sphinx.ext.viewcode']
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-#
-# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'pyrad'
-copyright = u'2017, Wichert Akkerman'
-author = u'Wichert Akkerman'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = u'2.1'
-# The full version, including alpha/beta/rc tags.
-release = u'2.1'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = []
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = True
-
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-#
-# html_theme = 'alabaster'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#
-# html_theme_options = {}
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-html_logo = '_static/logo.png'
-
-# -- Options for HTMLHelp output ------------------------------------------
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'pyraddoc'
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- #
- # 'papersize': 'letterpaper',
-
- # The font size ('10pt', '11pt' or '12pt').
- #
- # 'pointsize': '10pt',
-
- # Additional stuff for the LaTeX preamble.
- #
- # 'preamble': '',
-
- # Latex figure (float) alignment
- #
- # 'figure_align': 'htbp',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-# author, documentclass [howto, manual, or own class]).
-latex_documents = [
- (master_doc, 'pyrad.tex', u'pyrad Documentation',
- u'Wichert Akkerman', 'manual'),
-]
-
-
-# -- Options for manual page output ---------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- (master_doc, 'pyrad', u'pyrad Documentation',
- [author], 1)
-]
-
-
-# -- Options for Texinfo output -------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- (master_doc, 'pyrad', u'pyrad Documentation',
- author, 'pyrad', 'One line description of project.',
- 'Miscellaneous'),
-]
-
-
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'https://docs.python.org/': None}
diff --git a/docs/source/index.rst b/docs/source/index.rst
deleted file mode 100644
index 27998ec..0000000
--- a/docs/source/index.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-
-*********************************
-:mod:`pyrad` -- RADIUS for Python
-*********************************
-
-:Author: Wichert Akkerman
-:Version: |version|
-
-Introduction
-============
-
-pyrad is an implementation of a RADIUS client/server as described in RFC2865.
-It takes care of all the details like building RADIUS packets, sending
-them and decoding responses.
-
-Here is an example of doing a authentication request::
-
- from __future__ import print_function
- from pyrad.client import Client
- from pyrad.dictionary import Dictionary
- import pyrad.packet
-
- srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi",
- dict=Dictionary("dictionary"))
-
- # create request
- req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
- User_Name="wichert", NAS_Identifier="localhost")
- req["User-Password"] = req.PwCrypt("password")
-
- # send request
- reply = srv.SendPacket(req)
-
- if reply.code == pyrad.packet.AccessAccept:
- print("access accepted")
- else:
- print("access denied")
-
- print("Attributes returned by server:")
- for i in reply.keys():
- print("%s: %s" % (i, reply[i]))
-
-
-Requirements & Installation
-===========================
-
-pyrad requires Python 2.6 or later, or Python 3.2 or later
-
-Installing is simple; pyrad uses the standard distutils system for installing
-Python modules::
-
- python setup.py install
-
-
-API Documentation
-=================
-
-Per-module :mod:`pyrad` API documentation.
-
-.. toctree::
- :maxdepth: 2
-
- api/client
- api/dictionary
- api/host
- api/packet
- api/proxy
- api/server
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
diff --git a/example/acct.py b/example/acct.py
deleted file mode 100755
index c5a09fe..0000000
--- a/example/acct.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad.client import Client
-from pyrad.dictionary import Dictionary
-import random
-import socket
-import sys
-import pyrad.packet
-
-
-def SendPacket(srv, req):
- try:
- srv.SendPacket(req)
- except pyrad.client.Timeout:
- print("RADIUS server does not reply")
- sys.exit(1)
- except socket.error as error:
- print("Network error: " + error[1])
- sys.exit(1)
-
-srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi", dict=Dictionary("dictionary"))
-
-req = srv.CreateAcctPacket(User_Name="wichert")
-
-req["NAS-IP-Address"] = "192.168.1.10"
-req["NAS-Port"] = 0
-req["NAS-Identifier"] = "trillian"
-req["Called-Station-Id"] = "00-04-5F-00-0F-D1"
-req["Calling-Station-Id"] = "00-01-24-80-B3-9C"
-req["Framed-IP-Address"] = "10.0.0.100"
-
-print("Sending accounting start packet")
-req["Acct-Status-Type"] = "Start"
-SendPacket(srv, req)
-
-print("Sending accounting stop packet")
-req["Acct-Status-Type"] = "Stop"
-req["Acct-Input-Octets"] = random.randrange(2**10, 2**30)
-req["Acct-Output-Octets"] = random.randrange(2**10, 2**30)
-req["Acct-Session-Time"] = random.randrange(120, 3600)
-req["Acct-Terminate-Cause"] = random.choice(["User-Request", "Idle-Timeout"])
-SendPacket(srv, req)
diff --git a/example/auth.py b/example/auth.py
deleted file mode 100755
index 3c64987..0000000
--- a/example/auth.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad.client import Client
-from pyrad.dictionary import Dictionary
-import socket
-import sys
-import pyrad.packet
-
-srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi", dict=Dictionary("dictionary"))
-
-req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest, User_Name="wichert")
-
-req["NAS-IP-Address"] = "192.168.1.10"
-req["NAS-Port"] = 0
-req["Service-Type"] = "Login-User"
-req["NAS-Identifier"] = "trillian"
-req["Called-Station-Id"] = "00-04-5F-00-0F-D1"
-req["Calling-Station-Id"] = "00-01-24-80-B3-9C"
-req["Framed-IP-Address"] = "10.0.0.100"
-
-try:
- print("Sending authentication request")
- reply = srv.SendPacket(req)
-except pyrad.client.Timeout:
- print("RADIUS server does not reply")
- sys.exit(1)
-except socket.error as error:
- print("Network error: " + error[1])
- sys.exit(1)
-
-if reply.code == pyrad.packet.AccessAccept:
- print("Access accepted")
-else:
- print("Access denied")
-
-print("Attributes returned by server:")
-for i in reply.keys():
- print("%s: %s" % (i, reply[i]))
diff --git a/example/coa.py b/example/coa.py
deleted file mode 100644
index ec0479c..0000000
--- a/example/coa.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad.client import Client
-from pyrad import dictionary
-from pyrad import packet
-
-ADDRESS = "127.0.0.1"
-SECRET = b"Kah3choteereethiejeimaeziecumi"
-ATTRIBUTES = {
- "Acct-Session-Id": "1337"
-}
-
-# create coa client
-client = Client(server=ADDRESS, secret=SECRET, dict=dictionary.Dictionary("dictionary"))
-
-# set coa timeout
-client.timeout = 30
-
-# create coa request packet
-attributes = {k.replace("-", "_"): ATTRIBUTES[k] for k in ATTRIBUTES}
-
-# create coa request
-request = client.CreateCoAPacket(**attributes)
-# create disconnect request
-# request = client.CreateCoAPacket(code=packet.DisconnectRequest, **attributes)
-
-# send request
-result = client.SendPacket(request)
-print(result)
-print(result.code)
diff --git a/example/dictionary b/example/dictionary
deleted file mode 100644
index e6766be..0000000
--- a/example/dictionary
+++ /dev/null
@@ -1,405 +0,0 @@
-#
-# Version $Id: dictionary,v 1.1.1.1 2002/10/11 12:25:39 wichert Exp $
-#
-# This file contains dictionary translations for parsing
-# requests and generating responses. All transactions are
-# composed of Attribute/Value Pairs. The value of each attribute
-# is specified as one of 4 data types. Valid data types are:
-#
-# string - 0-253 octets
-# ipaddr - 4 octets in network byte order
-# integer - 32 bit value in big endian order (high byte first)
-# date - 32 bit value in big endian order - seconds since
-# 00:00:00 GMT, Jan. 1, 1970
-#
-# FreeRADIUS includes extended data types which are not defined
-# in RFC 2865 or RFC 2866. These data types are:
-#
-# abinary - Ascend's binary filter format.
-# octets - raw octets, printed and input as hex strings.
-# e.g.: 0x123456789abcdef
-#
-#
-# Enumerated values are stored in the user file with dictionary
-# VALUE translations for easy administration.
-#
-# Example:
-#
-# ATTRIBUTE VALUE
-# --------------- -----
-# Framed-Protocol = PPP
-# 7 = 1 (integer encoding)
-#
-
-#
-# Include compatibility dictionary for older users file. Move this
-# directive to the end of the file if you want to see the old names
-# in the logfiles too.
-#
-#$INCLUDE dictionary.compat # compability issues
-#$INCLUDE dictionary.acc
-#$INCLUDE dictionary.ascend
-#$INCLUDE dictionary.bay
-#$INCLUDE dictionary.cisco
-#$INCLUDE dictionary.livingston
-#$INCLUDE dictionary.microsoft
-#$INCLUDE dictionary.quintum
-#$INCLUDE dictionary.redback
-#$INCLUDE dictionary.shasta
-#$INCLUDE dictionary.shiva
-#$INCLUDE dictionary.tunnel
-#$INCLUDE dictionary.usr
-#$INCLUDE dictionary.versanet
-#$INCLUDE dictionary.erx
-#$INCLUDE dictionary.freeradius
-#$INCLUDE dictionary.alcatel
-
-#
-# Following are the proper new names. Use these.
-#
-ATTRIBUTE User-Name 1 string
-ATTRIBUTE User-Password 2 string
-ATTRIBUTE CHAP-Password 3 octets
-ATTRIBUTE NAS-IP-Address 4 ipaddr
-ATTRIBUTE NAS-Port 5 integer
-ATTRIBUTE Service-Type 6 integer
-ATTRIBUTE Framed-Protocol 7 integer
-ATTRIBUTE Framed-IP-Address 8 ipaddr
-ATTRIBUTE Framed-IP-Netmask 9 ipaddr
-ATTRIBUTE Framed-Routing 10 integer
-ATTRIBUTE Filter-Id 11 string
-ATTRIBUTE Framed-MTU 12 integer
-ATTRIBUTE Framed-Compression 13 integer
-ATTRIBUTE Login-IP-Host 14 ipaddr
-ATTRIBUTE Login-Service 15 integer
-ATTRIBUTE Login-TCP-Port 16 integer
-ATTRIBUTE Reply-Message 18 string
-ATTRIBUTE Callback-Number 19 string
-ATTRIBUTE Callback-Id 20 string
-ATTRIBUTE Framed-Route 22 string
-ATTRIBUTE Framed-IPX-Network 23 ipaddr
-ATTRIBUTE State 24 octets
-ATTRIBUTE Class 25 octets
-ATTRIBUTE Vendor-Specific 26 octets
-ATTRIBUTE Session-Timeout 27 integer
-ATTRIBUTE Idle-Timeout 28 integer
-ATTRIBUTE Termination-Action 29 integer
-ATTRIBUTE Called-Station-Id 30 string
-ATTRIBUTE Calling-Station-Id 31 string
-ATTRIBUTE NAS-Identifier 32 string
-ATTRIBUTE Proxy-State 33 octets
-ATTRIBUTE Login-LAT-Service 34 string
-ATTRIBUTE Login-LAT-Node 35 string
-ATTRIBUTE Login-LAT-Group 36 octets
-ATTRIBUTE Framed-AppleTalk-Link 37 integer
-ATTRIBUTE Framed-AppleTalk-Network 38 integer
-ATTRIBUTE Framed-AppleTalk-Zone 39 string
-
-ATTRIBUTE Acct-Status-Type 40 integer
-ATTRIBUTE Acct-Delay-Time 41 integer
-ATTRIBUTE Acct-Input-Octets 42 integer
-ATTRIBUTE Acct-Output-Octets 43 integer
-ATTRIBUTE Acct-Session-Id 44 string
-ATTRIBUTE Acct-Authentic 45 integer
-ATTRIBUTE Acct-Session-Time 46 integer
-ATTRIBUTE Acct-Input-Packets 47 integer
-ATTRIBUTE Acct-Output-Packets 48 integer
-ATTRIBUTE Acct-Terminate-Cause 49 integer
-ATTRIBUTE Acct-Multi-Session-Id 50 string
-ATTRIBUTE Acct-Link-Count 51 integer
-ATTRIBUTE Acct-Input-Gigawords 52 integer
-ATTRIBUTE Acct-Output-Gigawords 53 integer
-ATTRIBUTE Event-Timestamp 55 date
-
-ATTRIBUTE CHAP-Challenge 60 string
-ATTRIBUTE NAS-Port-Type 61 integer
-ATTRIBUTE Port-Limit 62 integer
-ATTRIBUTE Login-LAT-Port 63 integer
-
-ATTRIBUTE Acct-Tunnel-Connection 68 string
-
-ATTRIBUTE ARAP-Password 70 string
-ATTRIBUTE ARAP-Features 71 string
-ATTRIBUTE ARAP-Zone-Access 72 integer
-ATTRIBUTE ARAP-Security 73 integer
-ATTRIBUTE ARAP-Security-Data 74 string
-ATTRIBUTE Password-Retry 75 integer
-ATTRIBUTE Prompt 76 integer
-ATTRIBUTE Connect-Info 77 string
-ATTRIBUTE Configuration-Token 78 string
-ATTRIBUTE EAP-Message 79 string
-ATTRIBUTE Message-Authenticator 80 octets
-ATTRIBUTE ARAP-Challenge-Response 84 string # 10 octets
-ATTRIBUTE Acct-Interim-Interval 85 integer
-ATTRIBUTE NAS-Port-Id 87 string
-ATTRIBUTE Framed-Pool 88 string
-ATTRIBUTE NAS-IPv6-Address 95 octets # really IPv6
-ATTRIBUTE Framed-Interface-Id 96 octets # 8 octets
-ATTRIBUTE Framed-IPv6-Prefix 97 ipv6prefix # stupid format
-ATTRIBUTE Login-IPv6-Host 98 octets # really IPv6
-ATTRIBUTE Framed-IPv6-Route 99 string
-ATTRIBUTE Framed-IPv6-Pool 100 string
-ATTRIBUTE Delegated-IPv6-Prefix 123 ipv6prefix
-
-
-ATTRIBUTE Digest-Response 206 string
-ATTRIBUTE Digest-Attributes 207 octets # stupid format
-
-#
-# Experimental Non Protocol Attributes used by Cistron-Radiusd
-#
-
-# These attributes CAN go in the reply item list.
-ATTRIBUTE Fall-Through 500 integer
-ATTRIBUTE Exec-Program 502 string
-ATTRIBUTE Exec-Program-Wait 503 string
-
-# These attributes CANNOT go in the reply item list.
-ATTRIBUTE User-Category 1029 string
-ATTRIBUTE Group-Name 1030 string
-ATTRIBUTE Huntgroup-Name 1031 string
-ATTRIBUTE Simultaneous-Use 1034 integer
-ATTRIBUTE Strip-User-Name 1035 integer
-ATTRIBUTE Hint 1040 string
-ATTRIBUTE Pam-Auth 1041 string
-ATTRIBUTE Login-Time 1042 string
-ATTRIBUTE Stripped-User-Name 1043 string
-ATTRIBUTE Current-Time 1044 string
-ATTRIBUTE Realm 1045 string
-ATTRIBUTE No-Such-Attribute 1046 string
-ATTRIBUTE Packet-Type 1047 integer
-ATTRIBUTE Proxy-To-Realm 1048 string
-ATTRIBUTE Replicate-To-Realm 1049 string
-ATTRIBUTE Acct-Session-Start-Time 1050 date
-ATTRIBUTE Acct-Unique-Session-Id 1051 string
-ATTRIBUTE Client-IP-Address 1052 ipaddr
-ATTRIBUTE Ldap-UserDn 1053 string
-ATTRIBUTE NS-MTA-MD5-Password 1054 string
-ATTRIBUTE SQL-User-Name 1055 string
-ATTRIBUTE LM-Password 1057 octets
-ATTRIBUTE NT-Password 1058 octets
-ATTRIBUTE SMB-Account-CTRL 1059 integer
-ATTRIBUTE SMB-Account-CTRL-TEXT 1061 string
-ATTRIBUTE User-Profile 1062 string
-ATTRIBUTE Digest-Realm 1063 string
-ATTRIBUTE Digest-Nonce 1064 string
-ATTRIBUTE Digest-Method 1065 string
-ATTRIBUTE Digest-URI 1066 string
-ATTRIBUTE Digest-QOP 1067 string
-ATTRIBUTE Digest-Algorithm 1068 string
-ATTRIBUTE Digest-Body-Digest 1069 string
-ATTRIBUTE Digest-CNonce 1070 string
-ATTRIBUTE Digest-Nonce-Count 1071 string
-ATTRIBUTE Digest-User-Name 1072 string
-ATTRIBUTE Pool-Name 1073 string
-ATTRIBUTE Ldap-Group 1074 string
-ATTRIBUTE Module-Success-Message 1075 string
-ATTRIBUTE Module-Failure-Message 1076 string
-# X99-Fast 1077 integer
-
-#
-# Non-Protocol Attributes
-# These attributes are used internally by the server
-#
-ATTRIBUTE Auth-Type 1000 integer
-ATTRIBUTE Menu 1001 string
-ATTRIBUTE Termination-Menu 1002 string
-ATTRIBUTE Prefix 1003 string
-ATTRIBUTE Suffix 1004 string
-ATTRIBUTE Group 1005 string
-ATTRIBUTE Crypt-Password 1006 string
-ATTRIBUTE Connect-Rate 1007 integer
-ATTRIBUTE Add-Prefix 1008 string
-ATTRIBUTE Add-Suffix 1009 string
-ATTRIBUTE Expiration 1010 date
-ATTRIBUTE Autz-Type 1011 integer
-
-#
-# Integer Translations
-#
-
-# User Types
-
-VALUE Service-Type Login-User 1
-VALUE Service-Type Framed-User 2
-VALUE Service-Type Callback-Login-User 3
-VALUE Service-Type Callback-Framed-User 4
-VALUE Service-Type Outbound-User 5
-VALUE Service-Type Administrative-User 6
-VALUE Service-Type NAS-Prompt-User 7
-VALUE Service-Type Authenticate-Only 8
-VALUE Service-Type Callback-NAS-Prompt 9
-VALUE Service-Type Call-Check 10
-VALUE Service-Type Callback-Administrative 11
-
-# Framed Protocols
-
-VALUE Framed-Protocol PPP 1
-VALUE Framed-Protocol SLIP 2
-VALUE Framed-Protocol ARAP 3
-VALUE Framed-Protocol Gandalf-SLML 4
-VALUE Framed-Protocol Xylogics-IPX-SLIP 5
-VALUE Framed-Protocol X.75-Synchronous 6
-
-# Framed Routing Values
-
-VALUE Framed-Routing None 0
-VALUE Framed-Routing Broadcast 1
-VALUE Framed-Routing Listen 2
-VALUE Framed-Routing Broadcast-Listen 3
-
-# Framed Compression Types
-
-VALUE Framed-Compression None 0
-VALUE Framed-Compression Van-Jacobson-TCP-IP 1
-VALUE Framed-Compression IPX-Header-Compression 2
-VALUE Framed-Compression Stac-LZS 3
-
-# Login Services
-
-VALUE Login-Service Telnet 0
-VALUE Login-Service Rlogin 1
-VALUE Login-Service TCP-Clear 2
-VALUE Login-Service PortMaster 3
-VALUE Login-Service LAT 4
-VALUE Login-Service X25-PAD 5
-VALUE Login-Service X25-T3POS 6
-VALUE Login-Service TCP-Clear-Quiet 7
-
-# Login-TCP-Port (see /etc/services for more examples)
-
-VALUE Login-TCP-Port Telnet 23
-VALUE Login-TCP-Port Rlogin 513
-VALUE Login-TCP-Port Rsh 514
-
-# Status Types
-
-VALUE Acct-Status-Type Start 1
-VALUE Acct-Status-Type Stop 2
-VALUE Acct-Status-Type Interim-Update 3
-VALUE Acct-Status-Type Alive 3
-VALUE Acct-Status-Type Accounting-On 7
-VALUE Acct-Status-Type Accounting-Off 8
-# RFC 2867 Additional Status-Type Values
-VALUE Acct-Status-Type Tunnel-Start 9
-VALUE Acct-Status-Type Tunnel-Stop 10
-VALUE Acct-Status-Type Tunnel-Reject 11
-VALUE Acct-Status-Type Tunnel-Link-Start 12
-VALUE Acct-Status-Type Tunnel-Link-Stop 13
-VALUE Acct-Status-Type Tunnel-Link-Reject 14
-
-# Authentication Types
-
-VALUE Acct-Authentic RADIUS 1
-VALUE Acct-Authentic Local 2
-
-# Termination Options
-
-VALUE Termination-Action Default 0
-VALUE Termination-Action RADIUS-Request 1
-
-# NAS Port Types
-
-VALUE NAS-Port-Type Async 0
-VALUE NAS-Port-Type Sync 1
-VALUE NAS-Port-Type ISDN 2
-VALUE NAS-Port-Type ISDN-V120 3
-VALUE NAS-Port-Type ISDN-V110 4
-VALUE NAS-Port-Type Virtual 5
-VALUE NAS-Port-Type PIAFS 6
-VALUE NAS-Port-Type HDLC-Clear-Channel 7
-VALUE NAS-Port-Type X.25 8
-VALUE NAS-Port-Type X.75 9
-VALUE NAS-Port-Type G.3-Fax 10
-VALUE NAS-Port-Type SDSL 11
-VALUE NAS-Port-Type ADSL-CAP 12
-VALUE NAS-Port-Type ADSL-DMT 13
-VALUE NAS-Port-Type IDSL 14
-VALUE NAS-Port-Type Ethernet 15
-VALUE NAS-Port-Type xDSL 16
-VALUE NAS-Port-Type Cable 17
-VALUE NAS-Port-Type Wireless-Other 18
-VALUE NAS-Port-Type Wireless-802.11 19
-
-# Acct Terminate Causes, available in 3.3.2 and later
-
-VALUE Acct-Terminate-Cause User-Request 1
-VALUE Acct-Terminate-Cause Lost-Carrier 2
-VALUE Acct-Terminate-Cause Lost-Service 3
-VALUE Acct-Terminate-Cause Idle-Timeout 4
-VALUE Acct-Terminate-Cause Session-Timeout 5
-VALUE Acct-Terminate-Cause Admin-Reset 6
-VALUE Acct-Terminate-Cause Admin-Reboot 7
-VALUE Acct-Terminate-Cause Port-Error 8
-VALUE Acct-Terminate-Cause NAS-Error 9
-VALUE Acct-Terminate-Cause NAS-Request 10
-VALUE Acct-Terminate-Cause NAS-Reboot 11
-VALUE Acct-Terminate-Cause Port-Unneeded 12
-VALUE Acct-Terminate-Cause Port-Preempted 13
-VALUE Acct-Terminate-Cause Port-Suspended 14
-VALUE Acct-Terminate-Cause Service-Unavailable 15
-VALUE Acct-Terminate-Cause Callback 16
-VALUE Acct-Terminate-Cause User-Error 17
-VALUE Acct-Terminate-Cause Host-Request 18
-
-#VALUE Tunnel-Type L2TP 3
-#VALUE Tunnel-Medium-Type IP 1
-
-VALUE Prompt No-Echo 0
-VALUE Prompt Echo 1
-
-#
-# Non-Protocol Integer Translations
-#
-
-VALUE Auth-Type Local 0
-VALUE Auth-Type System 1
-VALUE Auth-Type SecurID 2
-VALUE Auth-Type Crypt-Local 3
-VALUE Auth-Type Reject 4
-VALUE Auth-Type ActivCard 5
-VALUE Auth-Type EAP 6
-VALUE Auth-Type ARAP 7
-
-#
-# Cistron extensions
-#
-VALUE Auth-Type Ldap 252
-VALUE Auth-Type Pam 253
-VALUE Auth-Type Accept 254
-
-VALUE Auth-Type PAP 1024
-VALUE Auth-Type CHAP 1025
-VALUE Auth-Type LDAP 1026
-VALUE Auth-Type PAM 1027
-VALUE Auth-Type MS-CHAP 1028
-VALUE Auth-Type Kerberos 1029
-VALUE Auth-Type CRAM 1030
-VALUE Auth-Type NS-MTA-MD5 1031
-VALUE Auth-Type CRAM 1032
-VALUE Auth-Type SMB 1033
-
-#
-# Authorization type, too.
-#
-VALUE Autz-Type Local 0
-
-#
-# Experimental Non-Protocol Integer Translations for Cistron-Radiusd
-#
-VALUE Fall-Through No 0
-VALUE Fall-Through Yes 1
-
-VALUE Packet-Type Access-Request 1
-VALUE Packet-Type Access-Accept 2
-VALUE Packet-Type Access-Reject 3
-VALUE Packet-Type Accounting-Request 4
-VALUE Packet-Type Accounting-Response 5
-VALUE Packet-Type Accounting-Status 6
-VALUE Packet-Type Password-Request 7
-VALUE Packet-Type Password-Accept 8
-VALUE Packet-Type Password-Reject 9
-VALUE Packet-Type Accounting-Message 10
-VALUE Packet-Type Access-Challenge 11
-VALUE Packet-Type Status-Server 12
-VALUE Packet-Type Status-Client 13
diff --git a/example/pyrad.log b/example/pyrad.log
deleted file mode 100644
index e69de29..0000000
diff --git a/example/server.py b/example/server.py
deleted file mode 100755
index d07a6a0..0000000
--- a/example/server.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad import dictionary, packet, server
-import logging
-
-logging.basicConfig(filename="pyrad.log", level="DEBUG",
- format="%(asctime)s [%(levelname)-8s] %(message)s")
-
-
-class FakeServer(server.Server):
-
- def HandleAuthPacket(self, pkt):
- print("Received an authentication request")
- print("Attributes: ")
- for attr in pkt.keys():
- print("%s: %s" % (attr, pkt[attr]))
-
- reply = self.CreateReplyPacket(pkt, **{
- "Service-Type": "Framed-User",
- "Framed-IP-Address": '192.168.0.1',
- "Framed-IPv6-Prefix": "fc66::1/64"
- })
-
- reply.code = packet.AccessAccept
- self.SendReplyPacket(pkt.fd, reply)
-
- def HandleAcctPacket(self, pkt):
-
- print("Received an accounting request")
- print("Attributes: ")
- for attr in pkt.keys():
- print("%s: %s" % (attr, pkt[attr]))
-
- reply = self.CreateReplyPacket(pkt)
- self.SendReplyPacket(pkt.fd, reply)
-
- def HandleCoaPacket(self, pkt):
-
- print("Received an coa request")
- print("Attributes: ")
- for attr in pkt.keys():
- print("%s: %s" % (attr, pkt[attr]))
-
- reply = self.CreateReplyPacket(pkt)
- self.SendReplyPacket(pkt.fd, reply)
-
- def HandleDisconnectPacket(self, pkt):
-
- print("Received an disconnect request")
- print("Attributes: ")
- for attr in pkt.keys():
- print("%s: %s" % (attr, pkt[attr]))
-
- reply = self.CreateReplyPacket(pkt)
- # COA NAK
- reply.code = 45
- self.SendReplyPacket(pkt.fd, reply)
-
-if __name__ == '__main__':
-
- # create server and read dictionary
- srv = FakeServer(dict=dictionary.Dictionary("dictionary"), coa_enabled=True)
-
- # add clients (address, secret, name)
- srv.hosts["127.0.0.1"] = server.RemoteHost("127.0.0.1", b"Kah3choteereethiejeimaeziecumi", "localhost")
- srv.BindToAddress("")
-
- # start server
- srv.Run()
diff --git a/index.txt b/index.txt
deleted file mode 100644
index acaf52b..0000000
--- a/index.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-.. Pyrad documentation master file, created by sphinx-quickstart on Sat Nov 15 00:01:13 2008.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-Welcome to Pyrad's documentation!
-=================================
-
-Contents:
-
-.. toctree::
- :maxdepth: 2
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100755
index 0000000..87b1df3
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,40 @@
+[build-system]
+requires = ["poetry>=1.0"]
+build-backend = "poetry.masonry.api"
+
+[tool.poetry]
+name = "pyrad"
+version= "2.4"
+readme = "README.rst"
+license = "BSD-3-Clause"
+description="RADIUS tools"
+authors = [
+ "Istvan Ruzman <istvan@ruzman.eu>",
+ "Christian Giese <developer@gicnet.de>",
+]
+keywords = ["AAA", "accounting", "authentication", "authorization", "RADIUS"]
+classifiers = [
+ "Development Status :: 6 - Mature",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: BSD License",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: System :: Systems Administration :: Authentication/Directory",
+]
+include = [
+ "example/*"
+]
+
+[tool.poetry.urls]
+repository = "https://github.com/pyradius/pyrad"
+
+[tool.poetry.dependencies]
+python = "^2.7 || ^3.6"
+six = "^1.15.0"
+netaddr = "^0.8"
+
+[tool.poetry.dev-dependencies]
+nose = "^0.10.0b1"
diff --git a/pyrad.egg-info/PKG-INFO b/pyrad.egg-info/PKG-INFO
new file mode 100644
index 0000000..155c290
--- /dev/null
+++ b/pyrad.egg-info/PKG-INFO
@@ -0,0 +1,98 @@
+Metadata-Version: 1.1
+Name: pyrad
+Version: 2.4
+Summary: RADIUS tools
+Home-page: https://github.com/pyradius/pyrad
+Author: Istvan Ruzman, Christian Giese
+Author-email: istvan@ruzman.eu, developer@gicnet.de
+License: BSD
+Description: .. image:: https://travis-ci.org/pyradius/pyrad.svg?branch=master
+ :target: https://travis-ci.org/pyradius/pyrad
+ .. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
+ :target: https://coveralls.io/github/pyradius/pyrad?branch=master
+ .. image:: https://img.shields.io/pypi/v/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+ .. image:: https://img.shields.io/pypi/pyversions/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+ .. image:: https://img.shields.io/pypi/dm/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+ .. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
+ :target: http://pyrad.readthedocs.io/en/latest/?badge=latest
+ :alt: Documentation Status
+ .. image:: https://img.shields.io/pypi/l/pyrad.svg
+ :target: https://pypi.python.org/pypi/pyrad
+
+ Introduction
+ ============
+
+ pyrad is an implementation of a RADIUS client/server as described in RFC2865.
+ It takes care of all the details like building RADIUS packets, sending
+ them and decoding responses.
+
+ Here is an example of doing a authentication request::
+
+ from __future__ import print_function
+ from pyrad.client import Client
+ from pyrad.dictionary import Dictionary
+ import pyrad.packet
+
+ srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi",
+ dict=Dictionary("dictionary"))
+
+ # create request
+ req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
+ User_Name="wichert", NAS_Identifier="localhost")
+ req["User-Password"] = req.PwCrypt("password")
+
+ # send request
+ reply = srv.SendPacket(req)
+
+ if reply.code == pyrad.packet.AccessAccept:
+ print("access accepted")
+ else:
+ print("access denied")
+
+ print("Attributes returned by server:")
+ for i in reply.keys():
+ print("%s: %s" % (i, reply[i]))
+
+
+
+ Requirements & Installation
+ ===========================
+
+ pyrad requires Python 2.7, or Python 3.6 or later
+
+ Installing is simple; pyrad uses the standard distutils system for installing
+ Python modules::
+
+ python setup.py install
+
+
+ Author, Copyright, Availability
+ ===============================
+
+ pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by
+ Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
+
+ This project is licensed under a BSD license.
+
+ Copyright and license information can be found in the LICENSE.txt file.
+
+ The current version and documentation can be found on pypi:
+ https://pypi.org/project/pyrad/
+
+ Bugs and wishes can be submitted in the pyrad issue tracker on github:
+ https://github.com/pyradius/pyrad/issues
+
+Keywords: radius,authentication
+Platform: UNKNOWN
+Classifier: Development Status :: 6 - Mature
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
diff --git a/pyrad.egg-info/SOURCES.txt b/pyrad.egg-info/SOURCES.txt
new file mode 100644
index 0000000..a2bd7b0
--- /dev/null
+++ b/pyrad.egg-info/SOURCES.txt
@@ -0,0 +1,23 @@
+README.rst
+pyproject.toml
+setup.cfg
+setup.py
+pyrad/__init__.py
+pyrad/bidict.py
+pyrad/client.py
+pyrad/client_async.py
+pyrad/curved.py
+pyrad/dictfile.py
+pyrad/dictionary.py
+pyrad/host.py
+pyrad/packet.py
+pyrad/proxy.py
+pyrad/server.py
+pyrad/server_async.py
+pyrad/tools.py
+pyrad.egg-info/PKG-INFO
+pyrad.egg-info/SOURCES.txt
+pyrad.egg-info/dependency_links.txt
+pyrad.egg-info/requires.txt
+pyrad.egg-info/top_level.txt
+pyrad.egg-info/zip-safe
\ No newline at end of file
diff --git a/pyrad.egg-info/dependency_links.txt b/pyrad.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pyrad.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/requirements.txt b/pyrad.egg-info/requires.txt
similarity index 100%
rename from requirements.txt
rename to pyrad.egg-info/requires.txt
diff --git a/pyrad.egg-info/top_level.txt b/pyrad.egg-info/top_level.txt
new file mode 100644
index 0000000..a39422a
--- /dev/null
+++ b/pyrad.egg-info/top_level.txt
@@ -0,0 +1 @@
+pyrad
diff --git a/pyrad.egg-info/zip-safe b/pyrad.egg-info/zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pyrad.egg-info/zip-safe
@@ -0,0 +1 @@
+
diff --git a/pyrad/__init__.py b/pyrad/__init__.py
index cd3e78a..feb21f8 100644
--- a/pyrad/__init__.py
+++ b/pyrad/__init__.py
@@ -38,8 +38,9 @@ This package contains four modules:
__docformat__ = 'epytext en'
-__author__ = 'Wichert Akkerman <wichert@wiggy.net>'
-__url__ = 'http://www.wiggy.net/code/pyrad.xhtml'
-__copyright__ = 'Copyright 2002-2007 Wichert Akkerman'
+__author__ = 'Christian Giese <developer@gicnet.de>'
+__url__ = 'http://pyrad.readthedocs.io/en/latest/?badge=latest'
+__copyright__ = 'Copyright 2002-2020 Wichert Akkerman and Christian Giese. All rights reserved.'
+__version__ = '2.4'
__all__ = ['client', 'dictionary', 'packet', 'server', 'tools', 'dictfile']
diff --git a/pyrad/client.py b/pyrad/client.py
index 9369010..6964963 100644
--- a/pyrad/client.py
+++ b/pyrad/client.py
@@ -4,13 +4,18 @@
__docformat__ = "epytext en"
+import hashlib
import select
import socket
import time
import six
+import struct
from pyrad import host
from pyrad import packet
+EAP_CODE_REQUEST = 1
+EAP_CODE_RESPONSE = 2
+EAP_TYPE_IDENTITY = 1
class Timeout(Exception):
"""Simple exception class which is raised when a timeout occurs
@@ -26,10 +31,10 @@ class Client(host.Host):
:ivar retries: number of times to retry sending a RADIUS request
:type retries: integer
:ivar timeout: number of seconds to wait for an answer
- :type timeout: integer
+ :type timeout: float
"""
def __init__(self, server, authport=1812, acctport=1813,
- coaport=3799, secret=six.b(''), dict=None):
+ coaport=3799, secret=six.b(''), dict=None, retries=3, timeout=5):
"""Constructor.
@@ -51,8 +56,9 @@ class Client(host.Host):
self.server = server
self.secret = secret
self._socket = None
- self.retries = 3
- self.timeout = 5
+ self.retries = retries
+ self.timeout = timeout
+ self._poll = select.poll()
def bind(self, addr):
"""Bind socket to an address.
@@ -67,14 +73,20 @@ class Client(host.Host):
self._socket.bind(addr)
def _SocketOpen(self):
+ try:
+ family = socket.getaddrinfo(self.server, 'www')[0][0]
+ except:
+ family = socket.AF_INET
if not self._socket:
- self._socket = socket.socket(socket.AF_INET,
+ self._socket = socket.socket(family,
socket.SOCK_DGRAM)
self._socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
+ self._poll.register(self._socket, select.POLLIN)
def _CloseSocket(self):
if self._socket:
+ self._poll.unregister(self._socket)
self._socket.close()
self._socket = None
@@ -86,7 +98,7 @@ class Client(host.Host):
dictionary and secret used for the client.
:return: a new empty packet instance
- :rtype: pyrad.packet.Packet
+ :rtype: pyrad.packet.AuthPacket
"""
return host.Host.CreateAuthPacket(self, secret=self.secret, **args)
@@ -101,7 +113,7 @@ class Client(host.Host):
:rtype: pyrad.packet.Packet
"""
return host.Host.CreateAcctPacket(self, secret=self.secret, **args)
-
+
def CreateCoAPacket(self, **args):
"""Create a new RADIUS packet.
This utility function creates a new RADIUS packet which can
@@ -134,16 +146,16 @@ class Client(host.Host):
pkt["Acct-Delay-Time"][0] + self.timeout
else:
pkt["Acct-Delay-Time"] = self.timeout
- self._socket.sendto(pkt.RequestPacket(), (self.server, port))
now = time.time()
waitto = now + self.timeout
+ self._socket.sendto(pkt.RequestPacket(), (self.server, port))
+
while now < waitto:
- ready = select.select([self._socket], [], [],
- (waitto - now))
+ ready = self._poll.poll((waitto - now) * 1000)
- if ready[0]:
+ if ready:
rawreply = self._socket.recv(4096)
else:
now = time.time()
@@ -170,7 +182,39 @@ class Client(host.Host):
:raise Timeout: RADIUS server does not reply
"""
if isinstance(pkt, packet.AuthPacket):
- return self._SendPacket(pkt, self.authport)
+ if pkt.auth_type == 'eap-md5':
+ # Creating EAP-Identity
+ password = pkt[2][0] if 2 in pkt else pkt[1][0]
+ pkt[79] = [struct.pack('!BBHB%ds' % len(password),
+ EAP_CODE_RESPONSE,
+ packet.CurrentID,
+ len(password) + 5,
+ EAP_TYPE_IDENTITY,
+ password)]
+ reply = self._SendPacket(pkt, self.authport)
+ if (
+ reply
+ and reply.code == packet.AccessChallenge
+ and pkt.auth_type == 'eap-md5'
+ ):
+ # Got an Access-Challenge
+ eap_code, eap_id, eap_size, eap_type, eap_md5 = struct.unpack(
+ '!BBHB%ds' % (len(reply[79][0]) - 5), reply[79][0]
+ )
+ # Sending back an EAP-Type-MD5-Challenge
+ # Thank god for http://www.secdev.org/python/eapy.py
+ client_pw = pkt[2][0] if 2 in pkt else pkt[1][0]
+ md5_challenge = hashlib.md5(
+ struct.pack('!B', eap_id) + client_pw + eap_md5[1:]
+ ).digest()
+ pkt[79] = [
+ struct.pack('!BBHBB', 2, eap_id, len(md5_challenge) + 6,
+ 4, len(md5_challenge)) + md5_challenge
+ ]
+ # Copy over Challenge-State
+ pkt[24] = reply[24]
+ reply = self._SendPacket(pkt, self.authport)
+ return reply
elif isinstance(pkt, packet.CoAPacket):
return self._SendPacket(pkt, self.coaport)
else:
diff --git a/pyrad/client_async.py b/pyrad/client_async.py
new file mode 100644
index 0000000..f2cedc8
--- /dev/null
+++ b/pyrad/client_async.py
@@ -0,0 +1,412 @@
+# client_async.py
+#
+# Copyright 2018-2020 Geaaru <geaaru<@>gmail.com>
+
+__docformat__ = "epytext en"
+
+from datetime import datetime
+import asyncio
+import six
+import logging
+import random
+
+from pyrad.packet import Packet, AuthPacket, AcctPacket, CoAPacket
+
+
+class DatagramProtocolClient(asyncio.Protocol):
+
+ def __init__(self, server, port, logger,
+ client, retries=3, timeout=30):
+ self.transport = None
+ self.port = port
+ self.server = server
+ self.logger = logger
+ self.retries = retries
+ self.timeout = timeout
+ self.client = client
+
+ # Map of pending requests
+ self.pending_requests = {}
+
+ # Use cryptographic-safe random generator as provided by the OS.
+ random_generator = random.SystemRandom()
+ self.packet_id = random_generator.randrange(0, 256)
+
+ self.timeout_future = None
+
+ async def __timeout_handler__(self):
+
+ try:
+
+ while True:
+
+ req2delete = []
+ now = datetime.now()
+ next_weak_up = self.timeout
+ # noinspection PyShadowingBuiltins
+ for id, req in self.pending_requests.items():
+
+ secs = (req['send_date'] - now).seconds
+ if secs > self.timeout:
+ if req['retries'] == self.retries:
+ self.logger.debug('[%s:%d] For request %d execute all retries', self.server, self.port, id)
+ req['future'].set_exception(
+ TimeoutError('Timeout on Reply')
+ )
+ req2delete.append(id)
+ else:
+ # Send again packet
+ req['send_date'] = now
+ req['retries'] += 1
+ self.logger.debug('[%s:%d] For request %d execute retry %d', self.server, self.port, id, req['retries'])
+ self.transport.sendto(req['packet'].RequestPacket())
+ elif next_weak_up > secs:
+ next_weak_up = secs
+
+ # noinspection PyShadowingBuiltins
+ for id in req2delete:
+ # Remove request for map
+ del self.pending_requests[id]
+
+ await asyncio.sleep(next_weak_up)
+
+ except asyncio.CancelledError:
+ pass
+
+ def send_packet(self, packet, future):
+ if packet.id in self.pending_requests:
+ raise Exception('Packet with id %d already present' % packet.id)
+
+ # Store packet on pending requests map
+ self.pending_requests[packet.id] = {
+ 'packet': packet,
+ 'creation_date': datetime.now(),
+ 'retries': 0,
+ 'future': future,
+ 'send_date': datetime.now()
+ }
+
+ # In queue packet raw on socket buffer
+ self.transport.sendto(packet.RequestPacket())
+
+ def connection_made(self, transport):
+ self.transport = transport
+ socket = transport.get_extra_info('socket')
+ self.logger.info(
+ '[%s:%d] Transport created with binding in %s:%d',
+ self.server, self.port,
+ socket.getsockname()[0],
+ socket.getsockname()[1]
+ )
+
+ pre_loop = asyncio.get_event_loop()
+ asyncio.set_event_loop(loop=self.client.loop)
+ # Start asynchronous timer handler
+ self.timeout_future = asyncio.ensure_future(
+ self.__timeout_handler__()
+ )
+ asyncio.set_event_loop(loop=pre_loop)
+
+ def error_received(self, exc):
+ self.logger.error('[%s:%d] Error received: %s', self.server, self.port, exc)
+
+ def connection_lost(self, exc):
+ if exc:
+ self.logger.warn('[%s:%d] Connection lost: %s', self.server, self.port, str(exc))
+ else:
+ self.logger.info('[%s:%d] Transport closed', self.server, self.port)
+
+ # noinspection PyUnusedLocal
+ def datagram_received(self, data, addr):
+ try:
+ reply = Packet(packet=data, dict=self.client.dict)
+
+ if reply and reply.id in self.pending_requests:
+ req = self.pending_requests[reply.id]
+ packet = req['packet']
+
+ reply.dict = packet.dict
+ reply.secret = packet.secret
+
+ if packet.VerifyReply(reply, data):
+ req['future'].set_result(reply)
+ # Remove request for map
+ del self.pending_requests[reply.id]
+ else:
+ self.logger.warn('[%s:%d] Ignore invalid reply for id %d. %s', self.server, self.port, reply.id)
+ else:
+ self.logger.warn('[%s:%d] Ignore invalid reply: %s', self.server, self.port, data)
+
+ except Exception as exc:
+ self.logger.error('[%s:%d] Error on decode packet: %s', self.server, self.port, exc)
+
+ async def close_transport(self):
+ if self.transport:
+ self.logger.debug('[%s:%d] Closing transport...', self.server, self.port)
+ self.transport.close()
+ self.transport = None
+ if self.timeout_future:
+ self.timeout_future.cancel()
+ await self.timeout_future
+ self.timeout_future = None
+
+ def create_id(self):
+ self.packet_id = (self.packet_id + 1) % 256
+ return self.packet_id
+
+ def __str__(self):
+ return 'DatagramProtocolClient(server?=%s, port=%d)' % (self.server, self.port)
+
+ # Used as protocol_factory
+ def __call__(self):
+ return self
+
+
+class ClientAsync:
+ """Basic RADIUS client.
+ This class implements a basic RADIUS client. It can send requests
+ to a RADIUS server, taking care of timeouts and retries, and
+ validate its replies.
+
+ :ivar retries: number of times to retry sending a RADIUS request
+ :type retries: integer
+ :ivar timeout: number of seconds to wait for an answer
+ :type timeout: integer
+ """
+ # noinspection PyShadowingBuiltins
+ def __init__(self, server, auth_port=1812, acct_port=1813,
+ coa_port=3799, secret=six.b(''), dict=None,
+ loop=None, retries=3, timeout=30,
+ logger_name='pyrad'):
+
+ """Constructor.
+
+ :param server: hostname or IP address of RADIUS server
+ :type server: string
+ :param auth_port: port to use for authentication packets
+ :type auth_port: integer
+ :param acct_port: port to use for accounting packets
+ :type acct_port: integer
+ :param coa_port: port to use for CoA packets
+ :type coa_port: integer
+ :param secret: RADIUS secret
+ :type secret: string
+ :param dict: RADIUS dictionary
+ :type dict: pyrad.dictionary.Dictionary
+ :param loop: Python loop handler
+ :type loop: asyncio event loop
+ """
+ if not loop:
+ self.loop = asyncio.get_event_loop()
+ else:
+ self.loop = loop
+ self.logger = logging.getLogger(logger_name)
+
+ self.server = server
+ self.secret = secret
+ self.retries = retries
+ self.timeout = timeout
+ self.dict = dict
+
+ self.auth_port = auth_port
+ self.protocol_auth = None
+
+ self.acct_port = acct_port
+ self.protocol_acct = None
+
+ self.protocol_coa = None
+ self.coa_port = coa_port
+
+ async def initialize_transports(self, enable_acct=False,
+ enable_auth=False, enable_coa=False,
+ local_addr=None, local_auth_port=None,
+ local_acct_port=None, local_coa_port=None):
+
+ task_list = []
+
+ if not enable_acct and not enable_auth and not enable_coa:
+ raise Exception('No transports selected')
+
+ if enable_acct and not self.protocol_acct:
+ self.protocol_acct = DatagramProtocolClient(
+ self.server,
+ self.acct_port,
+ self.logger, self,
+ retries=self.retries,
+ timeout=self.timeout
+ )
+ bind_addr = None
+ if local_addr and local_acct_port:
+ bind_addr = (local_addr, local_acct_port)
+
+ acct_connect = self.loop.create_datagram_endpoint(
+ self.protocol_acct,
+ reuse_port=True,
+ remote_addr=(self.server, self.acct_port),
+ local_addr=bind_addr
+ )
+ task_list.append(acct_connect)
+
+ if enable_auth and not self.protocol_auth:
+ self.protocol_auth = DatagramProtocolClient(
+ self.server,
+ self.auth_port,
+ self.logger, self,
+ retries=self.retries,
+ timeout=self.timeout
+ )
+ bind_addr = None
+ if local_addr and local_auth_port:
+ bind_addr = (local_addr, local_auth_port)
+
+ auth_connect = self.loop.create_datagram_endpoint(
+ self.protocol_auth,
+ reuse_port=True,
+ remote_addr=(self.server, self.auth_port),
+ local_addr=bind_addr
+ )
+ task_list.append(auth_connect)
+
+ if enable_coa and not self.protocol_coa:
+ self.protocol_coa = DatagramProtocolClient(
+ self.server,
+ self.coa_port,
+ self.logger, self,
+ retries=self.retries,
+ timeout=self.timeout
+ )
+ bind_addr = None
+ if local_addr and local_coa_port:
+ bind_addr = (local_addr, local_coa_port)
+
+ coa_connect = self.loop.create_datagram_endpoint(
+ self.protocol_coa,
+ reuse_port=True,
+ remote_addr=(self.server, self.coa_port),
+ local_addr=bind_addr
+ )
+ task_list.append(coa_connect)
+
+ await asyncio.ensure_future(
+ asyncio.gather(
+ *task_list,
+ return_exceptions=False,
+ ),
+ loop=self.loop
+ )
+
+ # noinspection SpellCheckingInspection
+ async def deinitialize_transports(self, deinit_coa=True,
+ deinit_auth=True,
+ deinit_acct=True):
+ if self.protocol_coa and deinit_coa:
+ await self.protocol_coa.close_transport()
+ del self.protocol_coa
+ self.protocol_coa = None
+ if self.protocol_auth and deinit_auth:
+ await self.protocol_auth.close_transport()
+ del self.protocol_auth
+ self.protocol_auth = None
+ if self.protocol_acct and deinit_acct:
+ await self.protocol_acct.close_transport()
+ del self.protocol_acct
+ self.protocol_acct = None
+
+ # noinspection PyPep8Naming
+ def CreateAuthPacket(self, **args):
+ """Create a new RADIUS packet.
+ This utility function creates a new RADIUS packet which can
+ be used to communicate with the RADIUS server this client
+ talks to. This is initializing the new packet with the
+ dictionary and secret used for the client.
+
+ :return: a new empty packet instance
+ :rtype: pyrad.packet.Packet
+ """
+ if not self.protocol_auth:
+ raise Exception('Transport not initialized')
+
+ return AuthPacket(dict=self.dict,
+ id=self.protocol_auth.create_id(),
+ secret=self.secret, **args)
+
+ # noinspection PyPep8Naming
+ def CreateAcctPacket(self, **args):
+ """Create a new RADIUS packet.
+ This utility function creates a new RADIUS packet which can
+ be used to communicate with the RADIUS server this client
+ talks to. This is initializing the new packet with the
+ dictionary and secret used for the client.
+
+ :return: a new empty packet instance
+ :rtype: pyrad.packet.Packet
+ """
+ if not self.protocol_acct:
+ raise Exception('Transport not initialized')
+
+ return AcctPacket(id=self.protocol_acct.create_id(),
+ dict=self.dict,
+ secret=self.secret, **args)
+
+ # noinspection PyPep8Naming
+ def CreateCoAPacket(self, **args):
+ """Create a new RADIUS packet.
+ This utility function creates a new RADIUS packet which can
+ be used to communicate with the RADIUS server this client
+ talks to. This is initializing the new packet with the
+ dictionary and secret used for the client.
+
+ :return: a new empty packet instance
+ :rtype: pyrad.packet.Packet
+ """
+
+ if not self.protocol_acct:
+ raise Exception('Transport not initialized')
+
+ return CoAPacket(id=self.protocol_coa.create_id(),
+ dict=self.dict,
+ secret=self.secret, **args)
+
+ # noinspection PyPep8Naming
+ # noinspection PyShadowingBuiltins
+ def CreatePacket(self, id, **args):
+ if not id:
+ raise Exception('Missing mandatory packet id')
+
+ return Packet(id=id, dict=self.dict,
+ secret=self.secret, **args)
+
+ # noinspection PyPep8Naming
+ def SendPacket(self, pkt):
+ """Send a packet to a RADIUS server.
+
+ :param pkt: the packet to send
+ :type pkt: pyrad.packet.Packet
+ :return: Future related with packet to send
+ :rtype: asyncio.Future
+ """
+
+ ans = asyncio.Future(loop=self.loop)
+
+ if isinstance(pkt, AuthPacket):
+ if not self.protocol_auth:
+ raise Exception('Transport not initialized')
+
+ self.protocol_auth.send_packet(pkt, ans)
+
+ elif isinstance(pkt, AcctPacket):
+ if not self.protocol_acct:
+ raise Exception('Transport not initialized')
+
+ self.protocol_acct.send_packet(pkt, ans)
+
+ elif isinstance(pkt, CoAPacket):
+ if not self.protocol_coa:
+ raise Exception('Transport not initialized')
+
+ self.protocol_coa.send_packet(pkt, ans)
+
+ else:
+ raise Exception('Unsupported packet')
+
+ return ans
diff --git a/pyrad/dictionary.py b/pyrad/dictionary.py
index 5cc42db..abe5263 100644
--- a/pyrad/dictionary.py
+++ b/pyrad/dictionary.py
@@ -55,6 +55,10 @@ The datatypes currently supported are:
+---------------+----------------------------------------------+
| byte | 8 bits unsigned number |
+---------------+----------------------------------------------+
+| tlv | Nested tag-length-value |
++---------------+----------------------------------------------+
+| integer64 | 64 bits unsigned number |
++---------------+----------------------------------------------+
These datatypes are parsed but not supported:
@@ -78,7 +82,7 @@ __docformat__ = 'epytext en'
DATATYPES = frozenset(['string', 'ipaddr', 'integer', 'date', 'octets',
'abinary', 'ipv6addr', 'ipv6prefix', 'short', 'byte',
- 'signed', 'ifid', 'ether'])
+ 'signed', 'ifid', 'ether', 'tlv', 'integer64'])
class ParseError(Exception):
@@ -86,7 +90,7 @@ class ParseError(Exception):
:ivar msg: Error message
:type msg: string
- :ivar linenumber: Line number on which the error occured
+ :ivar linenumber: Line number on which the error occurred
:type linenumber: integer
"""
@@ -111,7 +115,7 @@ class ParseError(Exception):
class Attribute(object):
- def __init__(self, name, code, datatype, vendor='', values={},
+ def __init__(self, name, code, datatype, is_sub_attribute=False, vendor='', values=None,
encrypt=0, has_tag=False):
if datatype not in DATATYPES:
raise ValueError('Invalid data type')
@@ -122,8 +126,12 @@ class Attribute(object):
self.encrypt = encrypt
self.has_tag = has_tag
self.values = bidict.BiDict()
- for (key, value) in values.items():
- self.values.Add(key, value)
+ self.sub_attributes = {}
+ self.parent = None
+ self.is_sub_attribute = is_sub_attribute
+ if values:
+ for (key, value) in values.items():
+ self.values.Add(key, value)
class Dictionary(object):
@@ -211,11 +219,28 @@ class Dictionary(object):
(attribute, code, datatype) = tokens[1:4]
- try:
- # todo: check if float like for extended attributes
- code = int(code, 0)
- except:
- return None
+ codes = code.split('.')
+
+ # Codes can be sent as hex, or octal or decimal string representations.
+ tmp = []
+ for c in codes:
+ if c.startswith('0x'):
+ tmp.append(int(c, 16))
+ elif c.startswith('0o'):
+ tmp.append(int(c, 8))
+ else:
+ tmp.append(int(c, 10))
+ codes = tmp
+
+ is_sub_attribute = (len(codes) > 1)
+ if len(codes) == 2:
+ code = int(codes[1])
+ parent_code = int(codes[0])
+ elif len(codes) == 1:
+ code = int(codes[0])
+ parent_code = None
+ else:
+ raise ParseError('nested tlvs are not supported')
datatype = datatype.split("[")[0]
@@ -224,12 +249,25 @@ class Dictionary(object):
file=state['file'],
line=state['line'])
if vendor:
- key = (self.vendors.GetForward(vendor), code)
+ if is_sub_attribute:
+ key = (self.vendors.GetForward(vendor), parent_code, code)
+ else:
+ key = (self.vendors.GetForward(vendor), code)
else:
- key = code
+ if is_sub_attribute:
+ key = (parent_code, code)
+ else:
+ key = code
self.attrindex.Add(attribute, key)
- self.attributes[attribute] = Attribute(attribute, code, datatype, vendor, encrypt=encrypt, has_tag=has_tag)
+ self.attributes[attribute] = Attribute(attribute, code, datatype, is_sub_attribute, vendor, encrypt=encrypt, has_tag=has_tag)
+ if datatype == 'tlv':
+ # save attribute in tlvs
+ state['tlvs'][code] = self.attributes[attribute]
+ if is_sub_attribute:
+ # save sub attribute in parent tlv and update their parent field
+ state['tlvs'][parent_code].sub_attributes[code] = attribute
+ self.attributes[attribute].parent = state['tlvs'][parent_code]
def __ParseValue(self, state, tokens, defer):
if len(tokens) != 4:
@@ -249,7 +287,7 @@ class Dictionary(object):
file=state['file'],
line=state['line'])
- if adef.type in ['integer','signed','short','byte']:
+ if adef.type in ['integer', 'signed', 'short', 'byte', 'integer64']:
value = int(value, 0)
value = tools.EncodeAttr(adef.type, value)
self.attributes[attr].values.Add(key, value)
@@ -332,7 +370,7 @@ class Dictionary(object):
state = {}
state['vendor'] = ''
-
+ state['tlvs'] = {}
self.defer_parse = []
for line in fil:
state['file'] = fil.File()
diff --git a/pyrad/packet.py b/pyrad/packet.py
index dfb9b58..995577a 100644
--- a/pyrad/packet.py
+++ b/pyrad/packet.py
@@ -4,9 +4,22 @@
#
# A RADIUS packet as defined in RFC 2138
-
+from collections import OrderedDict
import struct
-import random
+try:
+ import secrets
+ random_generator = secrets.SystemRandom()
+except ImportError:
+ import random
+ random_generator = random.SystemRandom()
+import hmac
+
+import sys
+if sys.version_info >= (3, 0):
+ hmac_new = lambda *x, **y: hmac.new(*x, digestmod='MD5', **y)
+else:
+ hmac_new = hmac.new
+
try:
import hashlib
md5_constructor = hashlib.md5
@@ -33,9 +46,6 @@ CoARequest = 43
CoAACK = 44
CoANAK = 45
-# Use cryptographic-safe random generator as provided by the OS.
-random_generator = random.SystemRandom()
-
# Current ID
CurrentID = random_generator.randrange(1, 255)
@@ -44,7 +54,7 @@ class PacketError(Exception):
pass
-class Packet(dict):
+class Packet(OrderedDict):
"""Packet acts like a standard python map to provide simple access
to the RADIUS attributes. Since RADIUS allows for repeated
attributes the value will always be a sequence. pyrad makes sure
@@ -60,21 +70,22 @@ class Packet(dict):
:obj:`AuthPacket` or :obj:`AcctPacket` classes.
"""
- def __init__(self, code=0, id=None, secret=six.b(''), authenticator=None, **attributes):
+ def __init__(self, code=0, id=None, secret=six.b(''), authenticator=None,
+ **attributes):
"""Constructor
:param dict: RADIUS dictionary
:type dict: pyrad.dictionary.Dictionary class
:param secret: secret needed to communicate with a RADIUS server
:type secret: string
- :param id: packet identifaction number
+ :param id: packet identification number
:type id: integer (8 bits)
:param code: packet type code
:type code: integer (8bits)
:param packet: raw packet to decode
:type packet: string
"""
- dict.__init__(self)
+ OrderedDict.__init__(self)
self.code = code
if id is not None:
self.id = id
@@ -85,21 +96,141 @@ class Packet(dict):
self.secret = secret
if authenticator is not None and \
not isinstance(authenticator, six.binary_type):
- raise TypeError('authenticator must be a binary string')
+ raise TypeError('authenticator must be a binary string')
self.authenticator = authenticator
+ self.message_authenticator = None
+ self.raw_packet = None
if 'dict' in attributes:
self.dict = attributes['dict']
if 'packet' in attributes:
- self.DecodePacket(attributes['packet'])
+ self.raw_packet = attributes['packet']
+ self.DecodePacket(self.raw_packet)
+
+ if 'message_authenticator' in attributes:
+ self.message_authenticator = attributes['message_authenticator']
for (key, value) in attributes.items():
- if key in ['dict', 'fd', 'packet']:
+ if key in [
+ 'dict', 'fd', 'packet',
+ 'message_authenticator',
+ ]:
continue
key = key.replace('_', '-')
self.AddAttribute(key, value)
+ def add_message_authenticator(self):
+
+ self.message_authenticator = True
+ # Maintain a zero octets content for md5 and hmac calculation.
+ self['Message-Authenticator'] = 16 * six.b('\00')
+
+ if self.id is None:
+ self.id = self.CreateID()
+
+ if self.authenticator is None and self.code == AccessRequest:
+ self.authenticator = self.CreateAuthenticator()
+ self._refresh_message_authenticator()
+
+ def get_message_authenticator(self):
+ self._refresh_message_authenticator()
+ return self.message_authenticator
+
+ def _refresh_message_authenticator(self):
+ hmac_constructor = hmac_new(self.secret)
+
+ # Maintain a zero octets content for md5 and hmac calculation.
+ self['Message-Authenticator'] = 16 * six.b('\00')
+ attr = self._PktEncodeAttributes()
+
+ header = struct.pack('!BBH', self.code, self.id,
+ (20 + len(attr)))
+
+ hmac_constructor.update(header[0:4])
+ if self.code in (AccountingRequest, DisconnectRequest,
+ CoARequest, AccountingResponse):
+ hmac_constructor.update(16 * six.b('\00'))
+ else:
+ # NOTE: self.authenticator on reply packet is initialized
+ # with request authenticator by design.
+ # For AccessAccept, AccessReject and AccessChallenge
+ # it is needed use original Authenticator.
+ # For AccessAccept, AccessReject and AccessChallenge
+ # it is needed use original Authenticator.
+ if self.authenticator is None:
+ raise Exception('No authenticator found')
+ hmac_constructor.update(self.authenticator)
+
+ hmac_constructor.update(attr)
+ self['Message-Authenticator'] = hmac_constructor.digest()
+
+ def verify_message_authenticator(self, secret=None,
+ original_authenticator=None,
+ original_code=None):
+ """Verify packet Message-Authenticator.
+
+ :return: False if verification failed else True
+ :rtype: boolean
+ """
+ if self.message_authenticator is None:
+ raise Exception('No Message-Authenticator AVP present')
+
+ prev_ma = self['Message-Authenticator']
+ # Set zero bytes for Message-Authenticator for md5 calculation
+ if secret is None and self.secret is None:
+ raise Exception('Missing secret for HMAC/MD5 verification')
+
+ if secret:
+ key = secret
+ else:
+ key = self.secret
+
+ # If there's a raw packet, use that to calculate the expected
+ # Message-Authenticator. While the Packet class keeps multiple
+ # instances of an attribute grouped together in the attribute list,
+ # other applications may not. Using _PktEncodeAttributes to get
+ # the attributes could therefore end up changing the attribute order
+ # because of the grouping Packet does, which would cause
+ # Message-Authenticator verification to fail. Using the raw packet
+ # instead, if present, ensures the verification is done using the
+ # attributes exactly as sent.
+ if self.raw_packet:
+ attr = self.raw_packet[20:]
+ attr = attr.replace(prev_ma[0], 16 * six.b('\00'))
+ else:
+ self['Message-Authenticator'] = 16 * six.b('\00')
+ attr = self._PktEncodeAttributes()
+
+ header = struct.pack('!BBH', self.code, self.id,
+ (20 + len(attr)))
+
+ hmac_constructor = hmac_new(key)
+ hmac_constructor.update(header)
+ if self.code in (AccountingRequest, DisconnectRequest,
+ CoARequest, AccountingResponse):
+ if original_code is None or original_code != StatusServer:
+ # TODO: Handle Status-Server response correctly.
+ hmac_constructor.update(16 * six.b('\00'))
+ elif self.code in (AccessAccept, AccessChallenge,
+ AccessReject):
+ if original_authenticator is None:
+ if self.authenticator:
+ # NOTE: self.authenticator on reply packet is initialized
+ # with request authenticator by design.
+ original_authenticator = self.authenticator
+ else:
+ raise Exception('Missing original authenticator')
+
+ hmac_constructor.update(original_authenticator)
+ else:
+ # On Access-Request and Status-Server use dynamic authenticator
+ hmac_constructor.update(self.authenticator)
+
+ hmac_constructor.update(attr)
+ self['Message-Authenticator'] = prev_ma[0]
+ return prev_ma[0] == hmac_constructor.digest()
+
def CreateReply(self, **attributes):
"""Create a new packet as a reply to this one. This method
makes sure the authenticator and secret are copied over
@@ -132,14 +263,12 @@ class Packet(dict):
if not isinstance(key, str):
return (key, values)
- key, _, tag = key.partition(":")
+ if not isinstance(values, (list, tuple)):
+ values = [values]
+ key, _, tag = key.partition(":")
attr = self.dict.attributes[key]
- if attr.vendor:
- key = (self.dict.vendors.GetForward(attr.vendor), attr.code)
- else:
- key = attr.code
-
+ key = self._EncodeKey(key)
if tag:
tag = struct.pack('B', int(tag))
if attr.type == "integer":
@@ -154,7 +283,7 @@ class Packet(dict):
return key
attr = self.dict.attributes[key]
- if attr.vendor:
+ if attr.vendor and not attr.is_sub_attribute: #sub attribute keys don't need vendor
return (self.dict.vendors.GetForward(attr.vendor), attr.code)
else:
return attr.code
@@ -175,50 +304,69 @@ class Packet(dict):
:param value: value
:type value: depends on type of attribute
"""
- if isinstance(value, list):
- (key, value) = self._EncodeKeyValues(key, value)
- self.setdefault(key, []).extend(value)
+ attr = self.dict.attributes[key.partition(':')[0]]
+
+ (key, value) = self._EncodeKeyValues(key, value)
+
+ if attr.is_sub_attribute:
+ tlv = self.setdefault(self._EncodeKey(attr.parent.name), {})
+ encoded = tlv.setdefault(key, [])
else:
- (key, value) = self._EncodeKeyValues(key, [value])
- value = value[0]
- self.setdefault(key, []).append(value)
+ encoded = self.setdefault(key, [])
+
+ encoded.extend(value)
+
+ def get(self, key, failobj=None):
+ try:
+ res = self.__getitem__(key)
+ except KeyError:
+ res = failobj
+ return res
def __getitem__(self, key):
if not isinstance(key, six.string_types):
- return dict.__getitem__(self, key)
+ return OrderedDict.__getitem__(self, key)
- values = dict.__getitem__(self, self._EncodeKey(key))
+ values = OrderedDict.__getitem__(self, self._EncodeKey(key))
attr = self.dict.attributes[key]
- res = []
- for v in values:
- res.append(self._DecodeValue(attr, v))
- return res
+ if attr.type == 'tlv': # return map from sub attribute code to its values
+ res = {}
+ for (sub_attr_key, sub_attr_val) in values.items():
+ sub_attr_name = attr.sub_attributes[sub_attr_key]
+ sub_attr = self.dict.attributes[sub_attr_name]
+ for v in sub_attr_val:
+ res.setdefault(sub_attr_name, []).append(self._DecodeValue(sub_attr, v))
+ return res
+ else:
+ res = []
+ for v in values:
+ res.append(self._DecodeValue(attr, v))
+ return res
def __contains__(self, key):
try:
- return dict.__contains__(self, self._EncodeKey(key))
+ return OrderedDict.__contains__(self, self._EncodeKey(key))
except KeyError:
return False
has_key = __contains__
def __delitem__(self, key):
- dict.__delitem__(self, self._EncodeKey(key))
+ OrderedDict.__delitem__(self, self._EncodeKey(key))
def __setitem__(self, key, item):
if isinstance(key, six.string_types):
- (key, item) = self._EncodeKeyValues(key, [item])
- dict.__setitem__(self, key, item)
+ (key, item) = self._EncodeKeyValues(key, item)
+ OrderedDict.__setitem__(self, key, item)
else:
- assert isinstance(item, list)
- dict.__setitem__(self, key, item)
+ OrderedDict.__setitem__(self, key, item)
def keys(self):
- return [self._DecodeKey(key) for key in dict.keys(self)]
+ return [self._DecodeKey(key) for key in OrderedDict.keys(self)]
@staticmethod
def CreateAuthenticator():
- """Create a packet autenticator. All RADIUS packets contain a sixteen
+ """Create a packet authenticator. All RADIUS packets contain a sixteen
byte authenticator which is used to authenticate replies from the
RADIUS server and in the password hiding algorithm. This function
returns a suitable random string that can be used as an authenticator.
@@ -228,7 +376,7 @@ class Packet(dict):
"""
data = []
- for i in range(16):
+ for _ in range(16):
data.append(random_generator.randrange(0, 256))
if six.PY3:
return bytes(data)
@@ -258,11 +406,15 @@ class Packet(dict):
assert(self.authenticator)
assert(self.secret is not None)
+ if self.message_authenticator:
+ self._refresh_message_authenticator()
+
attr = self._PktEncodeAttributes()
header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
authenticator = md5_constructor(header[0:4] + self.authenticator
- + attr + self.secret).digest()
+ + attr + self.secret).digest()
+
return header + authenticator + attr
def VerifyReply(self, reply, rawreply=None):
@@ -272,8 +424,17 @@ class Packet(dict):
if rawreply is None:
rawreply = reply.ReplyPacket()
+ attr = reply._PktEncodeAttributes()
+ # The Authenticator field in an Accounting-Response packet is called
+ # the Response Authenticator, and contains a one-way MD5 hash
+ # calculated over a stream of octets consisting of the Accounting
+ # Response Code, Identifier, Length, the Request Authenticator field
+ # from the Accounting-Request packet being replied to, and the
+ # response attributes if any, followed by the shared secret. The
+ # resulting 16 octet MD5 hash value is stored in the Authenticator
+ # field of the Accounting-Response packet.
hash = md5_constructor(rawreply[0:4] + self.authenticator +
- rawreply[20:] + self.secret).digest()
+ rawreply[20:] + self.secret).digest()
if hash != rawreply[4:20]:
return False
@@ -287,12 +448,47 @@ class Packet(dict):
return struct.pack('!BB', key, (len(value) + 2)) + value
+ def _PktEncodeTlv(self, tlv_key, tlv_value):
+ tlv_attr = self.dict.attributes[self._DecodeKey(tlv_key)]
+ curr_avp = six.b('')
+ avps = []
+ max_sub_attribute_len = max(map(lambda item: len(item[1]), tlv_value.items()))
+ for i in range(max_sub_attribute_len):
+ sub_attr_encoding = six.b('')
+ for (code, datalst) in tlv_value.items():
+ if i < len(datalst):
+ sub_attr_encoding += self._PktEncodeAttribute(code, datalst[i])
+ # split above 255. assuming len of one instance of all sub tlvs is lower than 255
+ if (len(sub_attr_encoding) + len(curr_avp)) < 245:
+ curr_avp += sub_attr_encoding
+ else:
+ avps.append(curr_avp)
+ curr_avp = sub_attr_encoding
+ avps.append(curr_avp)
+ tlv_avps = []
+ for avp in avps:
+ value = struct.pack('!BB', tlv_attr.code, (len(avp) + 2)) + avp
+ tlv_avps.append(value)
+ if tlv_attr.vendor:
+ vendor_avps = six.b('')
+ for avp in tlv_avps:
+ vendor_avps += struct.pack(
+ '!BBL', 26, (len(avp) + 6),
+ self.dict.vendors.GetForward(tlv_attr.vendor)
+ ) + avp
+ return vendor_avps
+ else:
+ return b''.join(tlv_avps)
+
def _PktEncodeAttributes(self):
result = six.b('')
for (code, datalst) in self.items():
- for data in datalst:
- result += self._PktEncodeAttribute(code, data)
-
+ attribute = self.dict.attributes.get(self._DecodeKey(code))
+ if attribute and attribute.type == 'tlv':
+ result += self._PktEncodeTlv(code, datalst)
+ else:
+ for data in datalst:
+ result += self._PktEncodeAttribute(code, data)
return result
def _PktDecodeVendorAttribute(self, data):
@@ -301,20 +497,36 @@ class Packet(dict):
if len(data) < 6:
return [(26, data)]
- (vendor, type, length) = struct.unpack('!LBB', data[:6])[0:3]
-
- tlvs = [((vendor, type), data[6:length+4])]
+ (vendor, atype, length) = struct.unpack('!LBB', data[:6])[0:3]
+ attribute = self.dict.attributes.get(self._DecodeKey((vendor, atype)))
+ try:
+ if attribute and attribute.type == 'tlv':
+ self._PktDecodeTlvAttribute((vendor, atype), data[6:length + 4])
+ tlvs = [] # tlv is added to the packet inside _PktDecodeTlvAttribute
+ else:
+ tlvs = [((vendor, atype), data[6:length + 4])]
+ except:
+ return [(26, data)]
sumlength = 4 + length
while len(data) > sumlength:
try:
- type, length = struct.unpack('!BB', data[sumlength:sumlength+2])[0:2]
+ atype, length = struct.unpack('!BB', data[sumlength:sumlength+2])[0:2]
except:
return [(26, data)]
- tlvs.append(((vendor, type), data[sumlength+2:sumlength+length]))
+ tlvs.append(((vendor, atype), data[sumlength+2:sumlength+length]))
sumlength += length
return tlvs
+ def _PktDecodeTlvAttribute(self, code, data):
+ sub_attributes = self.setdefault(code, {})
+ loc = 0
+
+ while loc < len(data):
+ atype, length = struct.unpack('!BB', data[loc:loc+2])[0:2]
+ sub_attributes.setdefault(atype, []).append(data[loc+2:loc+length])
+ loc += length
+
def DecodePacket(self, packet):
"""Initialize the object from raw packet data. Decode a packet as
received from the network and decode it.
@@ -325,6 +537,7 @@ class Packet(dict):
try:
(self.code, self.id, length, self.authenticator) = \
struct.unpack('!BBH16s', packet[0:20])
+
except struct.error:
raise PacketError('Packet header is corrupt')
if len(packet) != length:
@@ -346,9 +559,16 @@ class Packet(dict):
'Attribute length is too small (%d)' % attrlen)
value = packet[2:attrlen]
+ attribute = self.dict.attributes.get(self._DecodeKey(key))
if key == 26:
for (key, value) in self._PktDecodeVendorAttribute(value):
self.setdefault(key, []).append(value)
+ elif key == 80:
+ # POST: Message Authenticator AVP is present.
+ self.message_authenticator = True
+ self.setdefault(key, []).append(value)
+ elif attribute and attribute.type == 'tlv':
+ self._PktDecodeTlvAttribute(key,value)
else:
self.setdefault(key, []).append(value)
@@ -370,17 +590,22 @@ class Packet(dict):
# self.authenticator = self.CreateAuthenticator()
self.authenticator = 16 * six.b('\x00')
- salt = struct.pack('!H', random_generator.randrange(0, 65535))
- salt = chr(ord(salt[0]) | 1 << 7)+salt[1]
+ random_value = 32768 + random_generator.randrange(0, 32767)
+ if six.PY3:
+ salt_raw = struct.pack('!H', random_value )
+ salt = chr(salt_raw[0]) + chr(salt_raw[1])
+ else:
+ salt = struct.pack('!H', random_value )
+ salt = chr(ord(salt[0]) | 1 << 7)+salt[1]
+
+ result = six.b(salt)
length = struct.pack("B", len(value))
buf = length + value
if len(buf) % 16 != 0:
buf += six.b('\x00') * (16 - (len(buf) % 16))
- result = six.b(salt)
-
- last = self.authenticator + salt
+ last = self.authenticator + six.b(salt)
while buf:
hash = md5_constructor(self.secret + last).digest()
if six.PY3:
@@ -398,12 +623,12 @@ class Packet(dict):
class AuthPacket(Packet):
def __init__(self, code=AccessRequest, id=None, secret=six.b(''),
- authenticator=None, **attributes):
+ authenticator=None, auth_type='pap', **attributes):
"""Constructor
:param code: packet type code
:type code: integer (8bits)
- :param id: packet identifaction number
+ :param id: packet identification number
:type id: integer (8 bits)
:param secret: secret needed to communicate with a RADIUS server
:type secret: string
@@ -414,7 +639,9 @@ class AuthPacket(Packet):
:param packet: raw packet to decode
:type packet: string
"""
+
Packet.__init__(self, code, id, secret, authenticator, **attributes)
+ self.auth_type = auth_type
def CreateReply(self, **attributes):
"""Create a new packet as a reply to this one. This method
@@ -422,8 +649,8 @@ class AuthPacket(Packet):
to the new instance.
"""
return AuthPacket(AccessAccept, self.id,
- self.secret, self.authenticator, dict=self.dict,
- **attributes)
+ self.secret, self.authenticator, dict=self.dict,
+ auth_type=self.auth_type, **attributes)
def RequestPacket(self):
"""Create a ready-to-transmit authentication request packet.
@@ -433,21 +660,39 @@ class AuthPacket(Packet):
:return: raw packet
:rtype: string
"""
- attr = self._PktEncodeAttributes()
-
if self.authenticator is None:
self.authenticator = self.CreateAuthenticator()
if self.id is None:
self.id = self.CreateID()
+ if self.message_authenticator:
+ self._refresh_message_authenticator()
+
+ attr = self._PktEncodeAttributes()
+ if self.auth_type == 'eap-md5':
+ header = struct.pack(
+ '!BBH16s', self.code, self.id, (20 + 18 + len(attr)), self.authenticator
+ )
+ digest = hmac_new(
+ self.secret,
+ header
+ + attr
+ + struct.pack('!BB16s', 80, struct.calcsize('!BB16s'), b''),
+ ).digest()
+ return (
+ header
+ + attr
+ + struct.pack('!BB16s', 80, struct.calcsize('!BB16s'), digest)
+ )
+
header = struct.pack('!BBH16s', self.code, self.id,
- (20 + len(attr)), self.authenticator)
+ (20 + len(attr)), self.authenticator)
return header + attr
def PwDecrypt(self, password):
- """Unobfuscate a RADIUS password. RADIUS hides passwords in packets by
+ """Obfuscate a RADIUS password. RADIUS hides passwords in packets by
using an algorithm based on the MD5 hash of the packet authenticator
and RADIUS secret. This function reverses the obfuscation process.
@@ -486,7 +731,7 @@ class AuthPacket(Packet):
will not work.
:param password: plaintext password
- :type password: unicode stringn
+ :type password: unicode string
:return: obfuscated version of the password
:rtype: binary string
"""
@@ -500,7 +745,6 @@ class AuthPacket(Packet):
if len(password) % 16 != 0:
buf += six.b('\x00') * (16 - (len(password) % 16))
- hash = md5_constructor(self.secret + self.authenticator).digest()
result = six.b('')
last = self.authenticator
@@ -538,13 +782,25 @@ class AuthPacket(Packet):
return False
chapid = chap_password[0]
+ if six.PY3:
+ chapid = chr(chapid).encode('utf-8')
password = chap_password[1:]
challenge = self.authenticator
if 'CHAP-Challenge' in self:
challenge = self['CHAP-Challenge'][0]
+ return password == md5_constructor(chapid + userpwd + challenge).digest()
- return password == md5_constructor("%s%s%s" % (chapid, userpwd, challenge)).digest()
+ def VerifyAuthRequest(self):
+ """Verify request authenticator.
+
+ :return: True if verification failed else False
+ :rtype: boolean
+ """
+ assert(self.raw_packet)
+ hash = md5_constructor(self.raw_packet[0:4] + 16 * six.b('\x00') +
+ self.raw_packet[20:] + self.secret).digest()
+ return hash == self.authenticator
class AcctPacket(Packet):
@@ -553,14 +809,14 @@ class AcctPacket(Packet):
"""
def __init__(self, code=AccountingRequest, id=None, secret=six.b(''),
- authenticator=None, **attributes):
+ authenticator=None, **attributes):
"""Constructor
:param dict: RADIUS dictionary
:type dict: pyrad.dictionary.Dictionary class
:param secret: secret needed to communicate with a RADIUS server
:type secret: string
- :param id: packet identifaction number
+ :param id: packet identification number
:type id: integer (8 bits)
:param code: packet type code
:type code: integer (8bits)
@@ -568,8 +824,6 @@ class AcctPacket(Packet):
:type packet: string
"""
Packet.__init__(self, code, id, secret, authenticator, **attributes)
- if 'packet' in attributes:
- self.raw_packet = attributes['packet']
def CreateReply(self, **attributes):
"""Create a new packet as a reply to this one. This method
@@ -577,18 +831,20 @@ class AcctPacket(Packet):
to the new instance.
"""
return AcctPacket(AccountingResponse, self.id,
- self.secret, self.authenticator, dict=self.dict,
- **attributes)
+ self.secret, self.authenticator, dict=self.dict,
+ **attributes)
def VerifyAcctRequest(self):
"""Verify request authenticator.
- :return: True if verification failed else False
+ :return: False if verification failed else True
:rtype: boolean
"""
assert(self.raw_packet)
+
hash = md5_constructor(self.raw_packet[0:4] + 16 * six.b('\x00') +
- self.raw_packet[20:] + self.secret).digest()
+ self.raw_packet[20:] + self.secret).digest()
+
return hash == self.authenticator
def RequestPacket(self):
@@ -600,15 +856,21 @@ class AcctPacket(Packet):
:rtype: string
"""
- attr = self._PktEncodeAttributes()
-
if self.id is None:
self.id = self.CreateID()
+ if self.message_authenticator:
+ self._refresh_message_authenticator()
+
+ attr = self._PktEncodeAttributes()
header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
- self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') + attr
- + self.secret).digest()
- return header + self.authenticator + attr
+ self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') +
+ attr + self.secret).digest()
+
+ ans = header + self.authenticator + attr
+
+ return ans
+
class CoAPacket(Packet):
"""RADIUS CoA packets. This class is a specialization
@@ -623,7 +885,7 @@ class CoAPacket(Packet):
:type dict: pyrad.dictionary.Dictionary class
:param secret: secret needed to communicate with a RADIUS server
:type secret: string
- :param id: packet identifaction number
+ :param id: packet identification number
:type id: integer (8 bits)
:param code: packet type code
:type code: integer (8bits)
@@ -631,8 +893,6 @@ class CoAPacket(Packet):
:type packet: string
"""
Packet.__init__(self, code, id, secret, authenticator, **attributes)
- if 'packet' in attributes:
- self.raw_packet = attributes['packet']
def CreateReply(self, **attributes):
"""Create a new packet as a reply to this one. This method
@@ -640,18 +900,18 @@ class CoAPacket(Packet):
to the new instance.
"""
return CoAPacket(CoAACK, self.id,
- self.secret, self.authenticator, dict=self.dict,
- **attributes)
+ self.secret, self.authenticator, dict=self.dict,
+ **attributes)
def VerifyCoARequest(self):
"""Verify request authenticator.
- :return: True if verification failed else False
+ :return: False if verification failed else True
:rtype: boolean
"""
assert(self.raw_packet)
hash = md5_constructor(self.raw_packet[0:4] + 16 * six.b('\x00') +
- self.raw_packet[20:] + self.secret).digest()
+ self.raw_packet[20:] + self.secret).digest()
return hash == self.authenticator
def RequestPacket(self):
@@ -669,10 +929,18 @@ class CoAPacket(Packet):
self.id = self.CreateID()
header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
- self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') + attr
- + self.secret).digest()
+ self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') +
+ attr + self.secret).digest()
+
+ if self.message_authenticator:
+ self._refresh_message_authenticator()
+ attr = self._PktEncodeAttributes()
+ self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') +
+ attr + self.secret).digest()
+
return header + self.authenticator + attr
+
def CreateID():
"""Generate a packet ID.
diff --git a/pyrad/server.py b/pyrad/server.py
index 92434a5..d732798 100644
--- a/pyrad/server.py
+++ b/pyrad/server.py
@@ -103,6 +103,27 @@ class Server(host.Host):
for addr in addresses:
self.BindToAddress(addr)
+ def _GetAddrInfo(self, addr):
+ """Use getaddrinfo to lookup all addresses for each address.
+
+ Returns a list of tuples or an empty list:
+ [(family, address)]
+
+ :param addr: IP address to lookup
+ :type addr: string
+ """
+ results = set()
+ try:
+ tmp = socket.getaddrinfo(addr, 'www')
+ except socket.gaierror:
+ return []
+
+ for el in tmp:
+ results.add((el[0], el[4][0]))
+
+ return results
+
+
def BindToAddress(self, addr):
"""Add an address to listen to.
An empty string indicated you want to listen on all addresses.
@@ -110,23 +131,25 @@ class Server(host.Host):
:param addr: IP address to listen on
:type addr: string
"""
- if self.auth_enabled:
- authfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- authfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- authfd.bind((addr, self.authport))
- self.authfds.append(authfd)
-
- if self.acct_enabled:
- acctfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- acctfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- acctfd.bind((addr, self.acctport))
- self.acctfds.append(acctfd)
-
- if self.coa_enabled:
- coafd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- coafd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- coafd.bind((addr, self.coaport))
- self.coafds.append(coafd)
+ addrFamily = self._GetAddrInfo(addr)
+ for (family, address) in addrFamily:
+ if self.auth_enabled:
+ authfd = socket.socket(family, socket.SOCK_DGRAM)
+ authfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ authfd.bind((address, self.authport))
+ self.authfds.append(authfd)
+
+ if self.acct_enabled:
+ acctfd = socket.socket(family, socket.SOCK_DGRAM)
+ acctfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ acctfd.bind((address, self.acctport))
+ self.acctfds.append(acctfd)
+
+ if self.coa_enabled:
+ coafd = socket.socket(family, socket.SOCK_DGRAM)
+ coafd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ coafd.bind((address, self.coaport))
+ self.coafds.append(coafd)
def HandleAuthPacket(self, pkt):
@@ -169,6 +192,19 @@ class Server(host.Host):
:type pkt: Packet class instance
"""
+ def _AddSecret(self, pkt):
+ """Add secret to packets received and raise ServerPacketError
+ for unknown hosts.
+
+ :param pkt: packet to process
+ :type pkt: Packet class instance
+ """
+ if pkt.source[0] in self.hosts:
+ pkt.secret = self.hosts[pkt.source[0]].secret
+ elif '0.0.0.0' in self.hosts:
+ pkt.secret = self.hosts['0.0.0.0'].secret
+ else:
+ raise ServerPacketError('Received packet from unknown host')
def _HandleAuthPacket(self, pkt):
"""Process a packet received on the authentication port.
@@ -179,10 +215,7 @@ class Server(host.Host):
:param pkt: packet to process
:type pkt: Packet class instance
"""
- if pkt.source[0] not in self.hosts:
- raise ServerPacketError('Received packet from unknown host')
-
- pkt.secret = self.hosts[pkt.source[0]].secret
+ self._AddSecret(pkt)
if pkt.code != packet.AccessRequest:
raise ServerPacketError(
'Received non-authentication packet on authentication port')
@@ -197,10 +230,7 @@ class Server(host.Host):
:param pkt: packet to process
:type pkt: Packet class instance
"""
- if pkt.source[0] not in self.hosts:
- raise ServerPacketError('Received packet from unknown host')
-
- pkt.secret = self.hosts[pkt.source[0]].secret
+ self._AddSecret(pkt)
if pkt.code not in [packet.AccountingRequest,
packet.AccountingResponse]:
raise ServerPacketError(
@@ -216,9 +246,7 @@ class Server(host.Host):
:param pkt: packet to process
:type pkt: Packet class instance
"""
- if pkt.source[0] not in self.hosts:
- raise ServerPacketError('Received packet from unknown host')
-
+ self._AddSecret(pkt)
pkt.secret = self.hosts[pkt.source[0]].secret
if pkt.code == packet.CoARequest:
self.HandleCoaPacket(pkt)
@@ -281,15 +309,17 @@ class Server(host.Host):
:param fd: socket to read packet from
:type fd: socket class instance
"""
- if fd.fileno() in self._realauthfds:
+ if self.auth_enabled and fd.fileno() in self._realauthfds:
pkt = self._GrabPacket(lambda data, s=self: s.CreateAuthPacket(packet=data), fd)
self._HandleAuthPacket(pkt)
- elif fd.fileno() in self._realacctfds:
+ elif self.acct_enabled and fd.fileno() in self._realacctfds:
pkt = self._GrabPacket(lambda data, s=self: s.CreateAcctPacket(packet=data), fd)
self._HandleAcctPacket(pkt)
- else:
+ elif self.coa_enabled:
pkt = self._GrabPacket(lambda data, s=self: s.CreateCoAPacket(packet=data), fd)
self._HandleCoaPacket(pkt)
+ else:
+ raise ServerPacketError('Received packet for unknown handler')
def Run(self):
"""Main loop.
diff --git a/pyrad/server_async.py b/pyrad/server_async.py
new file mode 100644
index 0000000..8e36e2c
--- /dev/null
+++ b/pyrad/server_async.py
@@ -0,0 +1,336 @@
+# server_async.py
+#
+# Copyright 2018-2019 Geaaru <geaaru@gmail.com>
+
+import asyncio
+import logging
+import traceback
+
+from abc import abstractmethod, ABCMeta
+from enum import Enum
+from datetime import datetime
+from pyrad.packet import Packet, AccessAccept, AccessReject, \
+ AccountingRequest, AccountingResponse, \
+ DisconnectACK, DisconnectNAK, DisconnectRequest, CoARequest, \
+ CoAACK, CoANAK, AccessRequest, AuthPacket, AcctPacket, CoAPacket, \
+ PacketError
+
+from pyrad.server import ServerPacketError
+
+
+class ServerType(Enum):
+ Auth = 'Authentication'
+ Acct = 'Accounting'
+ Coa = 'Coa'
+
+
+class DatagramProtocolServer(asyncio.Protocol):
+
+ def __init__(self, ip, port, logger, server, server_type, hosts,
+ request_callback):
+ self.transport = None
+ self.ip = ip
+ self.port = port
+ self.logger = logger
+ self.server = server
+ self.hosts = hosts
+ self.server_type = server_type
+ self.request_callback = request_callback
+
+ def connection_made(self, transport):
+ self.transport = transport
+ self.logger.info('[%s:%d] Transport created', self.ip, self.port)
+
+ def connection_lost(self, exc):
+ if exc:
+ self.logger.warn('[%s:%d] Connection lost: %s', self.ip, self.port, str(exc))
+ else:
+ self.logger.info('[%s:%d] Transport closed', self.ip, self.port)
+
+ def send_response(self, reply, addr):
+ self.transport.sendto(reply.ReplyPacket(), addr)
+
+ def datagram_received(self, data, addr):
+ self.logger.debug('[%s:%d] Received %d bytes from %s', self.ip, self.port, len(data), addr)
+
+ receive_date = datetime.utcnow()
+
+ if addr[0] in self.hosts:
+ remote_host = self.hosts[addr[0]]
+ elif '0.0.0.0' in self.hosts:
+ remote_host = self.hosts['0.0.0.0']
+ else:
+ self.logger.warn('[%s:%d] Drop package from unknown source %s', self.ip, self.port, addr)
+ return
+
+ try:
+ self.logger.debug('[%s:%d] Received from %s packet: %s', self.ip, self.port, addr, data.hex())
+ req = Packet(packet=data, dict=self.server.dict)
+ except Exception as exc:
+ self.logger.error('[%s:%d] Error on decode packet: %s', self.ip, self.port, exc)
+ return
+
+ try:
+ if req.code in (AccountingResponse, AccessAccept, AccessReject, CoANAK, CoAACK, DisconnectNAK, DisconnectACK):
+ raise ServerPacketError('Invalid response packet %d' % req.code)
+
+ elif self.server_type == ServerType.Auth:
+ if req.code != AccessRequest:
+ raise ServerPacketError('Received non-auth packet on auth port')
+ req = AuthPacket(secret=remote_host.secret,
+ dict=self.server.dict,
+ packet=data)
+ if self.server.enable_pkt_verify:
+ if req.VerifyAuthRequest():
+ raise PacketError('Packet verification failed')
+
+ elif self.server_type == ServerType.Coa:
+ if req.code != DisconnectRequest and req.code != CoARequest:
+ raise ServerPacketError('Received non-coa packet on coa port')
+ req = CoAPacket(secret=remote_host.secret,
+ dict=self.server.dict,
+ packet=data)
+ if self.server.enable_pkt_verify:
+ if req.VerifyCoARequest():
+ raise PacketError('Packet verification failed')
+
+ elif self.server_type == ServerType.Acct:
+
+ if req.code != AccountingRequest:
+ raise ServerPacketError('Received non-acct packet on acct port')
+ req = AcctPacket(secret=remote_host.secret,
+ dict=self.server.dict,
+ packet=data)
+ if self.server.enable_pkt_verify:
+ if req.VerifyAcctRequest():
+ raise PacketError('Packet verification failed')
+
+ # Call request callback
+ self.request_callback(self, req, addr)
+ except Exception as exc:
+ if self.server.debug:
+ self.logger.exception('[%s:%d] Error for packet from %s', self.ip, self.port, addr)
+ else:
+ self.logger.error('[%s:%d] Error for packet from %s: %s', self.ip, self.port, addr, exc)
+
+ process_date = datetime.utcnow()
+ self.logger.debug('[%s:%d] Request from %s processed in %d ms', self.ip, self.port, addr, (process_date-receive_date).microseconds/1000)
+
+ def error_received(self, exc):
+ self.logger.error('[%s:%d] Error received: %s', self.ip, self.port, exc)
+
+ async def close_transport(self):
+ if self.transport:
+ self.logger.debug('[%s:%d] Close transport...', self.ip, self.port)
+ self.transport.close()
+ self.transport = None
+
+ def __str__(self):
+ return 'DatagramProtocolServer(ip=%s, port=%d)' % (self.ip, self.port)
+
+ # Used as protocol_factory
+ def __call__(self):
+ return self
+
+
+class ServerAsync(metaclass=ABCMeta):
+
+ def __init__(self, auth_port=1812, acct_port=1813,
+ coa_port=3799, hosts=None, dictionary=None,
+ loop=None, logger_name='pyrad',
+ enable_pkt_verify=False,
+ debug=False):
+
+ if not loop:
+ self.loop = asyncio.get_event_loop()
+ else:
+ self.loop = loop
+ self.logger = logging.getLogger(logger_name)
+
+ if hosts is None:
+ self.hosts = {}
+ else:
+ self.hosts = hosts
+
+ self.auth_port = auth_port
+ self.auth_protocols = []
+
+ self.acct_port = acct_port
+ self.acct_protocols = []
+
+ self.coa_port = coa_port
+ self.coa_protocols = []
+
+ self.dict = dictionary
+ self.enable_pkt_verify = enable_pkt_verify
+
+ self.debug = debug
+
+ def __request_handler__(self, protocol, req, addr):
+
+ try:
+ if protocol.server_type == ServerType.Acct:
+ self.handle_acct_packet(protocol, req, addr)
+ elif protocol.server_type == ServerType.Auth:
+ self.handle_auth_packet(protocol, req, addr)
+ elif protocol.server_type == ServerType.Coa and \
+ req.code == CoARequest:
+ self.handle_coa_packet(protocol, req, addr)
+ elif protocol.server_type == ServerType.Coa and \
+ req.code == DisconnectRequest:
+ self.handle_disconnect_packet(protocol, req, addr)
+ else:
+ self.logger.error('[%s:%s] Unexpected request found', protocol.ip, protocol.port)
+ except Exception as exc:
+ if self.debug:
+ self.logger.exception('[%s:%s] Unexpected error', protocol.ip, protocol.port)
+
+ else:
+ self.logger.error('[%s:%s] Unexpected error: %s', protocol.ip, protocol.port, exc)
+
+ def __is_present_proto__(self, ip, port):
+ if port == self.auth_port:
+ for proto in self.auth_protocols:
+ if proto.ip == ip:
+ return True
+ elif port == self.acct_port:
+ for proto in self.acct_protocols:
+ if proto.ip == ip:
+ return True
+ elif port == self.coa_port:
+ for proto in self.coa_protocols:
+ if proto.ip == ip:
+ return True
+ return False
+
+ # noinspection PyPep8Naming
+ @staticmethod
+ def CreateReplyPacket(pkt, **attributes):
+ """Create a reply packet.
+ Create a new packet which can be returned as a reply to a received
+ packet.
+
+ :param pkt: original packet
+ :type pkt: Packet instance
+ """
+ reply = pkt.CreateReply(**attributes)
+ return reply
+
+ async def initialize_transports(self, enable_acct=False,
+ enable_auth=False, enable_coa=False,
+ addresses=None):
+
+ task_list = []
+
+ if not enable_acct and not enable_auth and not enable_coa:
+ raise Exception('No transports selected')
+ if not addresses or len(addresses) == 0:
+ addresses = ['127.0.0.1']
+
+ # noinspection SpellCheckingInspection
+ for addr in addresses:
+
+ if enable_acct and not self.__is_present_proto__(addr, self.acct_port):
+ protocol_acct = DatagramProtocolServer(
+ addr,
+ self.acct_port,
+ self.logger, self,
+ ServerType.Acct,
+ self.hosts,
+ self.__request_handler__
+ )
+
+ bind_addr = (addr, self.acct_port)
+ acct_connect = self.loop.create_datagram_endpoint(
+ protocol_acct,
+ reuse_port=True,
+ local_addr=bind_addr
+ )
+ self.acct_protocols.append(protocol_acct)
+ task_list.append(acct_connect)
+
+ if enable_auth and not self.__is_present_proto__(addr, self.auth_port):
+ protocol_auth = DatagramProtocolServer(
+ addr,
+ self.auth_port,
+ self.logger, self,
+ ServerType.Auth,
+ self.hosts,
+ self.__request_handler__
+ )
+ bind_addr = (addr, self.auth_port)
+
+ auth_connect = self.loop.create_datagram_endpoint(
+ protocol_auth,
+ reuse_port=True,
+ local_addr=bind_addr
+ )
+ self.auth_protocols.append(protocol_auth)
+ task_list.append(auth_connect)
+
+ if enable_coa and not self.__is_present_proto__(addr, self.coa_port):
+ protocol_coa = DatagramProtocolServer(
+ addr,
+ self.coa_port,
+ self.logger, self,
+ ServerType.Coa,
+ self.hosts,
+ self.__request_handler__
+ )
+ bind_addr = (addr, self.coa_port)
+
+ coa_connect = self.loop.create_datagram_endpoint(
+ protocol_coa,
+ reuse_port=True,
+ local_addr=bind_addr
+ )
+ self.coa_protocols.append(protocol_coa)
+ task_list.append(coa_connect)
+
+ await asyncio.ensure_future(
+ asyncio.gather(
+ *task_list,
+ return_exceptions=False,
+ ),
+ loop=self.loop
+ )
+
+ # noinspection SpellCheckingInspection
+ async def deinitialize_transports(self, deinit_coa=True, deinit_auth=True, deinit_acct=True):
+
+ if deinit_coa:
+ for proto in self.coa_protocols:
+ await proto.close_transport()
+ del proto
+
+ self.coa_protocols = []
+
+ if deinit_auth:
+ for proto in self.auth_protocols:
+ await proto.close_transport()
+ del proto
+
+ self.auth_protocols = []
+
+ if deinit_acct:
+ for proto in self.acct_protocols:
+ await proto.close_transport()
+ del proto
+
+ self.acct_protocols = []
+
+ @abstractmethod
+ def handle_auth_packet(self, protocol, pkt, addr):
+ pass
+
+ @abstractmethod
+ def handle_acct_packet(self, protocol, pkt, addr):
+ pass
+
+ @abstractmethod
+ def handle_coa_packet(self, protocol, pkt, addr):
+ pass
+
+ @abstractmethod
+ def handle_disconnect_packet(self, protocol, pkt, addr):
+ pass
diff --git a/pyrad/tests/__init__.py b/pyrad/tests/__init__.py
deleted file mode 100644
index 0a99242..0000000
--- a/pyrad/tests/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import pyrad
-import sys
-
-pyrad # keep pyflakes happy
-home = sys.modules["pyrad"].__path__[0]
diff --git a/pyrad/tests/data/dictfiletest b/pyrad/tests/data/dictfiletest
deleted file mode 100644
index 6d908e9..0000000
--- a/pyrad/tests/data/dictfiletest
+++ /dev/null
@@ -1,5 +0,0 @@
-# A failing dictionary
-
-VALUE Not-Defined Undefined-Value 1
-
-
diff --git a/pyrad/tests/data/full b/pyrad/tests/data/full
deleted file mode 100644
index 1bdc870..0000000
--- a/pyrad/tests/data/full
+++ /dev/null
@@ -1,24 +0,0 @@
-# A simple dictionary
-
-ATTRIBUTE Test-String 1 string
-ATTRIBUTE Test-Octets 2 octets
-ATTRIBUTE Test-Integer 3 integer
-
-VALUE Test-Integer Zero 0
-VALUE Test-Integer One 1
-VALUE Test-Integer Two 2
-VALUE Test-Integer Three 3
-VALUE Test-Integer Four 4
-
-VENDOR Simplon 16
-
-
-BEGIN-VENDOR Simplon
-ATTRIBUTE Simplon-Number 1 integer
-
-VALUE Simplon-Number Zero 0
-VALUE Simplon-Number One 1
-VALUE Simplon-Number Two 2
-VALUE Simplon-Number Three 3
-VALUE Simplon-Number Four 4
-END-VENDOR Simplon
diff --git a/pyrad/tests/data/simple b/pyrad/tests/data/simple
deleted file mode 100644
index f9694c6..0000000
--- a/pyrad/tests/data/simple
+++ /dev/null
@@ -1,11 +0,0 @@
-# A simple dictionary
-
-ATTRIBUTE Test-String 1 string
-ATTRIBUTE Test-Octets 2 octets
-ATTRIBUTE Test-Integer 3 integer
-ATTRIBUTE Test-Ip-Address 4 ipaddr
-ATTRIBUTE Test-Ipv6-Address 5 ipv6addr
-ATTRIBUTE Test-If-Id 6 ifid
-ATTRIBUTE Test-Date 7 date
-ATTRIBUTE Test-Abinary 8 abinary
-
diff --git a/pyrad/tests/mock.py b/pyrad/tests/mock.py
deleted file mode 100644
index 47c6746..0000000
--- a/pyrad/tests/mock.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import fcntl
-import os
-from pyrad.packet import PacketError
-
-
-class MockPacket:
- reply = object()
-
- def __init__(self, code, verify=False, error=False):
- self.code = code
- self.data = {}
- self.verify = verify
- self.error = error
-
- def CreateReply(self, packet=None):
- if self.error:
- raise PacketError
- return self.reply
-
- def VerifyReply(self, reply, rawreply):
- return self.verify
-
- def RequestPacket(self):
- return "request packet"
-
- def __contains__(self, key):
- return key in self.data
- has_key = __contains__
-
- def __setitem__(self, key, value):
- self.data[key] = [value]
-
- def __getitem__(self, key):
- return self.data[key]
-
-
-class MockSocket:
- def __init__(self, domain, type, data=None):
- self.domain = domain
- self.type = type
- self.closed = False
- self.options = []
- self.address = None
- self.output = []
-
- if data is not None:
- (self.read_end, self.write_end) = os.pipe()
- fcntl.fcntl(self.write_end, fcntl.F_SETFL, os.O_NONBLOCK)
- os.write(self.write_end, data)
- self.data = data
- else:
- self.read_end = 1
- self.write_end = None
-
- def fileno(self):
- return self.read_end
-
- def bind(self, address):
- self.address = address
-
- def recv(self, buffer):
- return self.data[:buffer]
-
- def sendto(self, data, target):
- self.output.append((data, target))
-
- def setsockopt(self, level, opt, value):
- self.options.append((level, opt, value))
-
- def close(self):
- self.closed = True
-
-
-class MockFinished(Exception):
- pass
-
-
-class MockPoll:
- results = []
-
- def __init__(self):
- self.registry = []
-
- def register(self, fd, options):
- self.registry.append((fd, options))
-
- def poll(self):
- for result in self.results:
- yield result
- raise MockFinished
-
-
-def origkey(klass):
- return "_originals_" + klass.__name__
-
-
-def MockClassMethod(klass, name, myfunc=None):
- def func(self, *args, **kwargs):
- if not hasattr(self, "called"):
- self.called = []
- self.called.append((name, args, kwargs))
-
- key = origkey(klass)
- if not hasattr(klass, key):
- setattr(klass, key, {})
- getattr(klass, key)[name] = getattr(klass, name)
- if myfunc is None:
- setattr(klass, name, func)
- else:
- setattr(klass, name, myfunc)
-
-
-def UnmockClassMethods(klass):
- key = origkey(klass)
- if not hasattr(klass, key):
- return
- for (name, func) in getattr(klass, key).items():
- setattr(klass, name, func)
-
- delattr(klass, key)
-
-
-class MockFd:
- data = object()
- source = object()
-
- def __init__(self, fd=0):
- self.fd = fd
-
- def fileno(self):
- return self.fd
-
- def recvfrom(self, size):
- self.size = size
- return (self.data, self.source)
diff --git a/pyrad/tests/testBidict.py b/pyrad/tests/testBidict.py
deleted file mode 100644
index ad2f134..0000000
--- a/pyrad/tests/testBidict.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import operator
-import unittest
-from pyrad.bidict import BiDict
-
-
-class BiDictTests(unittest.TestCase):
- def setUp(self):
- self.bidict = BiDict()
-
- def testStartEmpty(self):
- self.assertEqual(len(self.bidict), 0)
- self.assertEqual(len(self.bidict.forward), 0)
- self.assertEqual(len(self.bidict.backward), 0)
-
- def testLength(self):
- self.assertEqual(len(self.bidict), 0)
- self.bidict.Add("from", "to")
- self.assertEqual(len(self.bidict), 1)
- del self.bidict["from"]
- self.assertEqual(len(self.bidict), 0)
-
- def testDeletion(self):
- self.assertRaises(KeyError, operator.delitem, self.bidict, "missing")
- self.bidict.Add("missing", "present")
- del self.bidict["missing"]
-
- def testBackwardDeletion(self):
- self.assertRaises(KeyError, operator.delitem, self.bidict, "missing")
- self.bidict.Add("missing", "present")
- del self.bidict["present"]
- self.assertEqual(self.bidict.HasForward("missing"), False)
-
- def testForwardAccess(self):
- self.bidict.Add("shake", "vanilla")
- self.bidict.Add("pie", "custard")
- self.assertEqual(self.bidict.HasForward("shake"), True)
- self.assertEqual(self.bidict.GetForward("shake"), "vanilla")
- self.assertEqual(self.bidict.HasForward("pie"), True)
- self.assertEqual(self.bidict.GetForward("pie"), "custard")
- self.assertEqual(self.bidict.HasForward("missing"), False)
- self.assertRaises(KeyError, self.bidict.GetForward, "missing")
-
- def testBackwardAccess(self):
- self.bidict.Add("shake", "vanilla")
- self.bidict.Add("pie", "custard")
- self.assertEqual(self.bidict.HasBackward("vanilla"), True)
- self.assertEqual(self.bidict.GetBackward("vanilla"), "shake")
- self.assertEqual(self.bidict.HasBackward("missing"), False)
- self.assertRaises(KeyError, self.bidict.GetBackward, "missing")
-
- def testItemAccessor(self):
- self.bidict.Add("shake", "vanilla")
- self.bidict.Add("pie", "custard")
- self.assertRaises(KeyError, operator.getitem, self.bidict, "missing")
- self.assertEquals(self.bidict["shake"], "vanilla")
- self.assertEquals(self.bidict["pie"], "custard")
diff --git a/pyrad/tests/testClient.py b/pyrad/tests/testClient.py
deleted file mode 100644
index 708525f..0000000
--- a/pyrad/tests/testClient.py
+++ /dev/null
@@ -1,178 +0,0 @@
-import socket
-import unittest
-import six
-from pyrad.client import Client
-from pyrad.client import Timeout
-from pyrad.packet import AuthPacket
-from pyrad.packet import AcctPacket
-from pyrad.packet import AccessRequest
-from pyrad.packet import AccountingRequest
-from pyrad.tests.mock import MockPacket
-from pyrad.tests.mock import MockSocket
-
-BIND_IP = "127.0.0.1"
-BIND_PORT = 53535
-
-
-class ConstructionTests(unittest.TestCase):
- def setUp(self):
- self.server = object()
-
- def testSimpleConstruction(self):
- client = Client(self.server)
- self.failUnless(client.server is self.server)
- self.assertEqual(client.authport, 1812)
- self.assertEqual(client.acctport, 1813)
- self.assertEqual(client.secret, six.b(''))
- self.assertEqual(client.retries, 3)
- self.assertEqual(client.timeout, 5)
- self.failUnless(client.dict is None)
-
- def testParameterOrder(self):
- marker = object()
- client = Client(self.server, 123, 456, 789, "secret", marker)
- self.failUnless(client.server is self.server)
- self.assertEqual(client.authport, 123)
- self.assertEqual(client.acctport, 456)
- self.assertEqual(client.coaport, 789)
- self.assertEqual(client.secret, "secret")
- self.failUnless(client.dict is marker)
-
- def testNamedParameters(self):
- marker = object()
- client = Client(server=self.server, authport=123, acctport=456,
- secret="secret", dict=marker)
- self.failUnless(client.server is self.server)
- self.assertEqual(client.authport, 123)
- self.assertEqual(client.acctport, 456)
- self.assertEqual(client.secret, "secret")
- self.failUnless(client.dict is marker)
-
-
-class SocketTests(unittest.TestCase):
- def setUp(self):
- self.server = object()
- self.client = Client(self.server)
- self.orgsocket = socket.socket
- socket.socket = MockSocket
-
- def tearDown(self):
- socket.socket = self.orgsocket
-
- def testReopen(self):
- self.client._SocketOpen()
- sock = self.client._socket
- self.client._SocketOpen()
- self.failUnless(sock is self.client._socket)
-
- def testBind(self):
- self.client.bind((BIND_IP, BIND_PORT))
- self.assertEqual(self.client._socket.address, (BIND_IP, BIND_PORT))
- self.assertEqual(self.client._socket.options,
- [(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)])
-
- def testBindClosesSocket(self):
- s = MockSocket(socket.AF_INET, socket.SOCK_DGRAM)
- self.client._socket = s
- self.client.bind((BIND_IP, BIND_PORT))
- self.assertEqual(s.closed, True)
-
- def testSendPacket(self):
- def MockSend(self, pkt, port):
- self._mock_pkt = pkt
- self._mock_port = port
-
- _SendPacket = Client._SendPacket
- Client._SendPacket = MockSend
-
- self.client.SendPacket(AuthPacket())
- self.assertEqual(self.client._mock_port, self.client.authport)
-
- self.client.SendPacket(AcctPacket())
- self.assertEqual(self.client._mock_port, self.client.acctport)
-
- Client._SendPacket = _SendPacket
-
- def testNoRetries(self):
- self.client.retries = 0
- self.assertRaises(Timeout, self.client._SendPacket, None, None)
-
- def testSingleRetry(self):
- self.client.retries = 1
- self.client.timeout = 0
- packet = MockPacket(AccessRequest)
- self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
- self.assertEqual(self.client._socket.output,
- [("request packet", (self.server, 432))])
-
- def testDoubleRetry(self):
- self.client.retries = 2
- self.client.timeout = 0
- packet = MockPacket(AccessRequest)
- self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
- self.assertEqual(self.client._socket.output,
- [("request packet", (self.server, 432)),
- ("request packet", (self.server, 432))])
-
- def testAuthDelay(self):
- self.client.retries = 2
- self.client.timeout = 1
- packet = MockPacket(AccessRequest)
- self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
- self.failIf("Acct-Delay-Time" in packet)
-
- def testSingleAccountDelay(self):
- self.client.retries = 2
- self.client.timeout = 1
- packet = MockPacket(AccountingRequest)
- self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
- self.assertEqual(packet["Acct-Delay-Time"], [1])
-
- def testDoubleAccountDelay(self):
- self.client.retries = 3
- self.client.timeout = 1
- packet = MockPacket(AccountingRequest)
- self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
- self.assertEqual(packet["Acct-Delay-Time"], [2])
-
- def testIgnorePacketError(self):
- self.client.retries = 1
- self.client.timeout = 1
- self.client._socket = MockSocket(1, 2, six.b("valid reply"))
- packet = MockPacket(AccountingRequest, verify=True, error=True)
- self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-
- def testValidReply(self):
- self.client.retries = 1
- self.client.timeout = 1
- self.client._socket = MockSocket(1, 2, six.b("valid reply"))
- packet = MockPacket(AccountingRequest, verify=True)
- reply = self.client._SendPacket(packet, 432)
- self.failUnless(reply is packet.reply)
-
- def testInvalidReply(self):
- self.client.retries = 1
- self.client.timeout = 1
- self.client._socket = MockSocket(1, 2, six.b("invalid reply"))
- packet = MockPacket(AccountingRequest, verify=False)
- self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-
-
-class OtherTests(unittest.TestCase):
- def setUp(self):
- self.server = object()
- self.client = Client(self.server, secret=six.b('zeer geheim'))
-
- def testCreateAuthPacket(self):
- packet = self.client.CreateAuthPacket(id=15)
- self.failUnless(isinstance(packet, AuthPacket))
- self.failUnless(packet.dict is self.client.dict)
- self.assertEqual(packet.id, 15)
- self.assertEqual(packet.secret, six.b('zeer geheim'))
-
- def testCreateAcctPacket(self):
- packet = self.client.CreateAcctPacket(id=15)
- self.failUnless(isinstance(packet, AcctPacket))
- self.failUnless(packet.dict is self.client.dict)
- self.assertEqual(packet.id, 15)
- self.assertEqual(packet.secret, six.b('zeer geheim'))
diff --git a/pyrad/tests/testDictionary.py b/pyrad/tests/testDictionary.py
deleted file mode 100644
index dd6a45e..0000000
--- a/pyrad/tests/testDictionary.py
+++ /dev/null
@@ -1,303 +0,0 @@
-import unittest
-import operator
-import os
-from six import StringIO
-from pyrad.tests import home
-from pyrad.dictionary import Attribute
-from pyrad.dictionary import Dictionary
-from pyrad.dictionary import ParseError
-from pyrad.tools import DecodeAttr
-from pyrad.dictfile import DictFile
-
-
-class AttributeTests(unittest.TestCase):
- def testInvalidDataType(self):
- self.assertRaises(ValueError, Attribute, 'name', 'code', 'datatype')
-
- def testConstructionParameters(self):
- attr = Attribute('name', 'code', 'integer', 'vendor')
- self.assertEqual(attr.name, 'name')
- self.assertEqual(attr.code, 'code')
- self.assertEqual(attr.type, 'integer')
- self.assertEqual(attr.vendor, 'vendor')
- self.assertEqual(len(attr.values), 0)
-
- def testNamedConstructionParameters(self):
- attr = Attribute(name='name', code='code', datatype='integer',
- vendor='vendor')
- self.assertEqual(attr.name, 'name')
- self.assertEqual(attr.code, 'code')
- self.assertEqual(attr.type, 'integer')
- self.assertEqual(attr.vendor, 'vendor')
- self.assertEqual(len(attr.values), 0)
-
- def testValues(self):
- attr = Attribute('name', 'code', 'integer', 'vendor',
- dict(pie='custard', shake='vanilla'))
- self.assertEqual(len(attr.values), 2)
- self.assertEqual(attr.values['shake'], 'vanilla')
-
-
-class DictionaryInterfaceTests(unittest.TestCase):
- def testEmptyDictionary(self):
- dict = Dictionary()
- self.assertEqual(len(dict), 0)
-
- def testContainment(self):
- dict = Dictionary()
- self.assertEqual('test' in dict, False)
- self.assertEqual(dict.has_key('test'), False)
- dict.attributes['test'] = 'dummy'
- self.assertEqual('test' in dict, True)
- self.assertEqual(dict.has_key('test'), True)
-
- def testReadonlyContainer(self):
- import six
- dict = Dictionary()
- self.assertRaises(TypeError,
- operator.setitem, dict, 'test', 'dummy')
- self.assertRaises(AttributeError,
- operator.attrgetter('clear'), dict)
- self.assertRaises(AttributeError,
- operator.attrgetter('update'), dict)
-
-
-class DictionaryParsingTests(unittest.TestCase):
- def setUp(self):
- self.path = os.path.join(home, 'tests', 'data')
- self.dict = Dictionary(os.path.join(self.path, 'simple'))
-
- def testParseEmptyDictionary(self):
- dict = Dictionary(StringIO(''))
- self.assertEqual(len(dict), 0)
-
- def testParseMultipleDictionaries(self):
- dict = Dictionary(StringIO(''))
- self.assertEqual(len(dict), 0)
- one = StringIO('ATTRIBUTE Test-First 1 string')
- two = StringIO('ATTRIBUTE Test-Second 2 string')
- dict = Dictionary(StringIO(''), one, two)
- self.assertEqual(len(dict), 2)
-
- def testParseSimpleDictionary(self):
- self.assertEqual(len(self.dict), 8)
- values = [
- ('Test-String', 1, 'string'),
- ('Test-Octets', 2, 'octets'),
- ('Test-Integer', 3, 'integer'),
- ('Test-Ip-Address', 4, 'ipaddr'),
- ('Test-Ipv6-Address', 5, 'ipv6addr'),
- ('Test-If-Id', 6, 'ifid'),
- ('Test-Date', 7, 'date'),
- ('Test-Abinary', 8, 'abinary'),
- ]
-
- for (attr, code, type) in values:
- attr = self.dict[attr]
- self.assertEqual(attr.code, code)
- self.assertEqual(attr.type, type)
-
- def testAttributeTooFewColumnsError(self):
- try:
- self.dict.ReadDictionary(
- StringIO('ATTRIBUTE Oops-Too-Few-Columns'))
- except ParseError as e:
- self.assertEqual('attribute' in str(e), True)
- else:
- self.fail()
-
- def testAttributeUnknownTypeError(self):
- try:
- self.dict.ReadDictionary(StringIO('ATTRIBUTE Test-Type 1 dummy'))
- except ParseError as e:
- self.assertEqual('dummy' in str(e), True)
- else:
- self.fail()
-
- def testAttributeUnknownVendorError(self):
- try:
- self.dict.ReadDictionary(StringIO('ATTRIBUTE Test-Type 1 Simplon'))
- except ParseError as e:
- self.assertEqual('Simplon' in str(e), True)
- else:
- self.fail()
-
- def testAttributeOptions(self):
- self.dict.ReadDictionary(StringIO(
- 'ATTRIBUTE Option-Type 1 string has_tag,encrypt=1'))
- self.assertEqual(self.dict['Option-Type'].has_tag, True)
- self.assertEqual(self.dict['Option-Type'].encrypt, 1)
-
- def testAttributeEncryptionError(self):
- try:
- self.dict.ReadDictionary(StringIO(
- 'ATTRIBUTE Test-Type 1 string encrypt=4'))
- except ParseError as e:
- self.assertEqual('encrypt' in str(e), True)
- else:
- self.fail()
-
- def testValueTooFewColumnsError(self):
- try:
- self.dict.ReadDictionary(StringIO('VALUE Oops-Too-Few-Columns'))
- except ParseError as e:
- self.assertEqual('value' in str(e), True)
- else:
- self.fail()
-
- def testValueForUnknownAttributeError(self):
- try:
- self.dict.ReadDictionary(StringIO(
- 'VALUE Test-Attribute Test-Text 1'))
- except ParseError as e:
- self.assertEqual('unknown attribute' in str(e), True)
- else:
- self.fail()
-
- def testIntegerValueParsing(self):
- self.assertEqual(len(self.dict['Test-Integer'].values), 0)
- self.dict.ReadDictionary(StringIO('VALUE Test-Integer Value-Six 5'))
- self.assertEqual(len(self.dict['Test-Integer'].values), 1)
- self.assertEqual(
- DecodeAttr('integer',
- self.dict['Test-Integer'].values['Value-Six']),
- 5)
-
- def testStringValueParsing(self):
- self.assertEqual(len(self.dict['Test-String'].values), 0)
- self.dict.ReadDictionary(StringIO(
- 'VALUE Test-String Value-Custard custardpie'))
- self.assertEqual(len(self.dict['Test-String'].values), 1)
- self.assertEqual(
- DecodeAttr('string',
- self.dict['Test-String'].values['Value-Custard']),
- 'custardpie')
-
- def testVenderTooFewColumnsError(self):
- try:
- self.dict.ReadDictionary(StringIO('VENDOR Simplon'))
- except ParseError as e:
- self.assertEqual('vendor' in str(e), True)
- else:
- self.fail()
-
- def testVendorParsing(self):
- self.assertRaises(ParseError, self.dict.ReadDictionary,
- StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
- self.dict.ReadDictionary(StringIO('VENDOR Simplon 42'))
- self.assertEqual(self.dict.vendors['Simplon'], 42)
- self.dict.ReadDictionary(StringIO(
- 'ATTRIBUTE Test-Type 1 integer Simplon'))
- self.assertEquals(self.dict.attrindex['Test-Type'], (42, 1))
-
- def testVendorOptionError(self):
- self.assertRaises(ParseError, self.dict.ReadDictionary,
- StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
- try:
- self.dict.ReadDictionary(StringIO('VENDOR Simplon 42 badoption'))
- except ParseError as e:
- self.assertEqual('option' in str(e), True)
- else:
- self.fail()
-
- def testVendorFormatError(self):
- self.assertRaises(ParseError, self.dict.ReadDictionary,
- StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
- try:
- self.dict.ReadDictionary(StringIO(
- 'VENDOR Simplon 42 format=5,4'))
- except ParseError as e:
- self.assertEqual('format' in str(e), True)
- else:
- self.fail()
-
- def testVendorFormatSyntaxError(self):
- self.assertRaises(ParseError, self.dict.ReadDictionary,
- StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
- try:
- self.dict.ReadDictionary(StringIO(
- 'VENDOR Simplon 42 format=a,1'))
- except ParseError as e:
- self.assertEqual('Syntax' in str(e), True)
- else:
- self.fail()
-
- def testBeginVendorTooFewColumns(self):
- try:
- self.dict.ReadDictionary(StringIO('BEGIN-VENDOR'))
- except ParseError as e:
- self.assertEqual('begin-vendor' in str(e), True)
- else:
- self.fail()
-
- def testBeginVendorUnknownVendor(self):
- try:
- self.dict.ReadDictionary(StringIO('BEGIN-VENDOR Simplon'))
- except ParseError as e:
- self.assertEqual('Simplon' in str(e), True)
- else:
- self.fail()
-
- def testBeginVendorParsing(self):
- self.dict.ReadDictionary(StringIO(
- 'VENDOR Simplon 42\n'
- 'BEGIN-VENDOR Simplon\n'
- 'ATTRIBUTE Test-Type 1 integer'))
- self.assertEquals(self.dict.attrindex['Test-Type'], (42, 1))
-
- def testEndVendorUnknownVendor(self):
- try:
- self.dict.ReadDictionary(StringIO('END-VENDOR'))
- except ParseError as e:
- self.assertEqual('end-vendor' in str(e), True)
- else:
- self.fail()
-
- def testEndVendorUnbalanced(self):
- try:
- self.dict.ReadDictionary(StringIO(
- 'VENDOR Simplon 42\n'
- 'BEGIN-VENDOR Simplon\n'
- 'END-VENDOR Oops\n'))
- except ParseError as e:
- self.assertEqual('Oops' in str(e), True)
- else:
- self.fail()
-
- def testEndVendorParsing(self):
- self.dict.ReadDictionary(StringIO(
- 'VENDOR Simplon 42\n'
- 'BEGIN-VENDOR Simplon\n'
- 'END-VENDOR Simplon\n'
- 'ATTRIBUTE Test-Type 1 integer'))
- self.assertEquals(self.dict.attrindex['Test-Type'], 1)
-
- def testInclude(self):
- try:
- self.dict.ReadDictionary(StringIO(
- '$INCLUDE this_file_does_not_exist\n'
- 'VENDOR Simplon 42\n'
- 'BEGIN-VENDOR Simplon\n'
- 'END-VENDOR Simplon\n'
- 'ATTRIBUTE Test-Type 1 integer'))
- except IOError as e:
- self.assertEqual('this_file_does_not_exist' in str(e), True)
- else:
- self.fail()
-
- def testDictFilePostParse(self):
- f = DictFile(StringIO(
- 'VENDOR Simplon 42\n'))
- for _ in f:
- pass
- self.assertEquals(f.File(), '')
- self.assertEquals(f.Line(), -1)
-
- def testDictFileParseError(self):
- tmpdict = Dictionary()
- try:
- tmpdict.ReadDictionary(os.path.join(self.path, 'dictfiletest'))
- except ParseError as e:
- self.assertEquals('dictfiletest' in str(e), True)
- else:
- self.fail()
diff --git a/pyrad/tests/testHost.py b/pyrad/tests/testHost.py
deleted file mode 100644
index ec51deb..0000000
--- a/pyrad/tests/testHost.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import unittest
-from pyrad.host import Host
-from pyrad.packet import Packet
-from pyrad.packet import AuthPacket
-from pyrad.packet import AcctPacket
-
-
-class ConstructionTests(unittest.TestCase):
- def testSimpleConstruction(self):
- host = Host()
- self.assertEqual(host.authport, 1812)
- self.assertEqual(host.acctport, 1813)
-
- def testParameterOrder(self):
- host = Host(123, 456, 789, 101)
- self.assertEqual(host.authport, 123)
- self.assertEqual(host.acctport, 456)
- self.assertEqual(host.coaport, 789)
- self.assertEqual(host.dict, 101)
-
- def testNamedParameters(self):
- host = Host(authport=123, acctport=456, coaport=789, dict=101)
- self.assertEqual(host.authport, 123)
- self.assertEqual(host.acctport, 456)
- self.assertEqual(host.coaport, 789)
- self.assertEqual(host.dict, 101)
-
-
-class PacketCreationTests(unittest.TestCase):
- def setUp(self):
- self.host = Host()
-
- def testCreatePacket(self):
- packet = self.host.CreatePacket(id=15)
- self.failUnless(isinstance(packet, Packet))
- self.failUnless(packet.dict is self.host.dict)
- self.assertEqual(packet.id, 15)
-
- def testCreateAuthPacket(self):
- packet = self.host.CreateAuthPacket(id=15)
- self.failUnless(isinstance(packet, AuthPacket))
- self.failUnless(packet.dict is self.host.dict)
- self.assertEqual(packet.id, 15)
-
- def testCreateAcctPacket(self):
- packet = self.host.CreateAcctPacket(id=15)
- self.failUnless(isinstance(packet, AcctPacket))
- self.failUnless(packet.dict is self.host.dict)
- self.assertEqual(packet.id, 15)
-
-
-class MockPacket:
- packet = object()
- replypacket = object()
- source = object()
-
- def Packet(self):
- return self.packet
-
- def ReplyPacket(self):
- return self.replypacket
-
-
-class MockFd:
- data = None
- target = None
-
- def sendto(self, data, target):
- self.data = data
- self.target = target
-
-
-class PacketSendTest(unittest.TestCase):
- def setUp(self):
- self.host = Host()
- self.fd = MockFd()
- self.packet = MockPacket()
-
- def testSendPacket(self):
- self.host.SendPacket(self.fd, self.packet)
- self.failUnless(self.fd.data is self.packet.packet)
- self.failUnless(self.fd.target is self.packet.source)
-
- def testSendReplyPacket(self):
- self.host.SendReplyPacket(self.fd, self.packet)
- self.failUnless(self.fd.data is self.packet.replypacket)
- self.failUnless(self.fd.target is self.packet.source)
diff --git a/pyrad/tests/testPacket.py b/pyrad/tests/testPacket.py
deleted file mode 100644
index d5705a0..0000000
--- a/pyrad/tests/testPacket.py
+++ /dev/null
@@ -1,413 +0,0 @@
-import os
-import unittest
-import six
-from pyrad import packet
-from pyrad.tests import home
-from pyrad.dictionary import Dictionary
-
-
-class UtilityTests(unittest.TestCase):
- def testGenerateID(self):
- id = packet.CreateID()
- self.failUnless(isinstance(id, int))
- newid = packet.CreateID()
- self.assertNotEqual(id, newid)
-
-
-class PacketConstructionTests(unittest.TestCase):
- klass = packet.Packet
-
- def setUp(self):
- self.path = os.path.join(home, 'tests', 'data')
- self.dict = Dictionary(os.path.join(self.path, 'simple'))
-
- def testBasicConstructor(self):
- pkt = self.klass()
- self.failUnless(isinstance(pkt.code, int))
- self.failUnless(isinstance(pkt.id, int))
- self.failUnless(isinstance(pkt.secret, six.binary_type))
-
- def testNamedConstructor(self):
- pkt = self.klass(code=26, id=38, secret=six.b('secret'),
- authenticator=six.b('authenticator'),
- dict='fakedict')
- self.assertEqual(pkt.code, 26)
- self.assertEqual(pkt.id, 38)
- self.assertEqual(pkt.secret, six.b('secret'))
- self.assertEqual(pkt.authenticator, six.b('authenticator'))
- self.assertEqual(pkt.dict, 'fakedict')
-
- def testConstructWithDictionary(self):
- pkt = self.klass(dict=self.dict)
- self.failUnless(pkt.dict is self.dict)
-
- def testConstructorIgnoredParameters(self):
- marker = []
- pkt = self.klass(fd=marker)
- self.failIf(getattr(pkt, 'fd', None) is marker)
-
- def testSecretMustBeBytestring(self):
- self.assertRaises(TypeError, self.klass, secret=six.u('secret'))
-
- def testConstructorWithAttributes(self):
- pkt = self.klass(dict=self.dict, Test_String='this works')
- self.assertEqual(pkt['Test-String'], ['this works'])
-
-
-class PacketTests(unittest.TestCase):
- def setUp(self):
- self.path = os.path.join(home, 'tests', 'data')
- self.dict = Dictionary(os.path.join(self.path, 'full'))
- self.packet = packet.Packet(id=0, secret=six.b('secret'),
- authenticator=six.b('01234567890ABCDEF'), dict=self.dict)
-
- def testCreateReply(self):
- reply = self.packet.CreateReply(Test_Integer=10)
- self.assertEqual(reply.id, self.packet.id)
- self.assertEqual(reply.secret, self.packet.secret)
- self.assertEqual(reply.authenticator, self.packet.authenticator)
- self.assertEqual(reply['Test-Integer'], [10])
-
- def testAttributeAccess(self):
- self.packet['Test-Integer'] = 10
- self.assertEqual(self.packet['Test-Integer'], [10])
- self.assertEqual(self.packet[3], [six.b('\x00\x00\x00\x0a')])
-
- self.packet['Test-String'] = 'dummy'
- self.assertEqual(self.packet['Test-String'], ['dummy'])
- self.assertEqual(self.packet[1], [six.b('dummy')])
-
- def testAttributeValueAccess(self):
- self.packet['Test-Integer'] = 'Three'
- self.assertEqual(self.packet['Test-Integer'], ['Three'])
- self.assertEqual(self.packet[3], [six.b('\x00\x00\x00\x03')])
-
- def testVendorAttributeAccess(self):
- self.packet['Simplon-Number'] = 10
- self.assertEqual(self.packet['Simplon-Number'], [10])
- self.assertEqual(self.packet[(16, 1)], [six.b('\x00\x00\x00\x0a')])
-
- self.packet['Simplon-Number'] = 'Four'
- self.assertEqual(self.packet['Simplon-Number'], ['Four'])
- self.assertEqual(self.packet[(16, 1)], [six.b('\x00\x00\x00\x04')])
-
- def testRawAttributeAccess(self):
- marker = [six.b('')]
- self.packet[1] = marker
- self.failUnless(self.packet[1] is marker)
- self.packet[(16, 1)] = marker
- self.failUnless(self.packet[(16, 1)] is marker)
-
- def testHasKey(self):
- self.assertEqual(self.packet.has_key('Test-String'), False)
- self.assertEqual('Test-String' in self.packet, False)
- self.packet['Test-String'] = 'dummy'
- self.assertEqual(self.packet.has_key('Test-String'), True)
- self.assertEqual(self.packet.has_key(1), True)
- self.assertEqual(1 in self.packet, True)
-
- def testHasKeyWithUnknownKey(self):
- self.assertEqual(self.packet.has_key('Unknown-Attribute'), False)
- self.assertEqual('Unknown-Attribute' in self.packet, False)
-
- def testDelItem(self):
- self.packet['Test-String'] = 'dummy'
- del self.packet['Test-String']
- self.assertEqual(self.packet.has_key('Test-String'), False)
- self.packet['Test-String'] = 'dummy'
- del self.packet[1]
- self.assertEqual(self.packet.has_key('Test-String'), False)
-
- def testKeys(self):
- self.assertEqual(self.packet.keys(), [])
- self.packet['Test-String'] = 'dummy'
- self.assertEqual(self.packet.keys(), ['Test-String'])
- self.packet['Test-Integer'] = 10
- self.assertEqual(self.packet.keys(), ['Test-String', 'Test-Integer'])
- dict.__setitem__(self.packet, 12345, None)
- self.assertEqual(self.packet.keys(),
- ['Test-String', 'Test-Integer', 12345])
-
- def testCreateAuthenticator(self):
- a = packet.Packet.CreateAuthenticator()
- self.failUnless(isinstance(a, six.binary_type))
- self.assertEqual(len(a), 16)
-
- b = packet.Packet.CreateAuthenticator()
- self.assertNotEqual(a, b)
-
- def testGenerateID(self):
- id = self.packet.CreateID()
- self.failUnless(isinstance(id, int))
- newid = self.packet.CreateID()
- self.assertNotEqual(id, newid)
-
- def testReplyPacket(self):
- reply = self.packet.ReplyPacket()
- self.assertEqual(reply,
- six.b('\x00\x00\x00\x14\xb0\x5e\x4b\xfb\xcc\x1c'
- '\x8c\x8e\xc4\x72\xac\xea\x87\x45\x63\xa7'))
-
- def testVerifyReply(self):
- reply = self.packet.CreateReply()
- self.assertEqual(self.packet.VerifyReply(reply), True)
-
- reply.id += 1
- self.assertEqual(self.packet.VerifyReply(reply), False)
- reply.id = self.packet.id
-
- reply.secret = six.b('different')
- self.assertEqual(self.packet.VerifyReply(reply), False)
- reply.secret = self.packet.secret
-
- reply.authenticator = six.b('X') * 16
- self.assertEqual(self.packet.VerifyReply(reply), False)
- reply.authenticator = self.packet.authenticator
-
- def testPktEncodeAttribute(self):
- encode = self.packet._PktEncodeAttribute
-
- # Encode a normal attribute
- self.assertEqual(
- encode(1, six.b('value')),
- six.b('\x01\x07value'))
- # Encode a vendor attribute
- self.assertEqual(
- encode((1, 2), six.b('value')),
- six.b('\x1a\x0d\x00\x00\x00\x01\x02\x07value'))
-
- def testPktEncodeAttributes(self):
- self.packet[1] = [six.b('value')]
- self.assertEqual(self.packet._PktEncodeAttributes(),
- six.b('\x01\x07value'))
-
- self.packet.clear()
- self.packet[(1, 2)] = [six.b('value')]
- self.assertEqual(self.packet._PktEncodeAttributes(),
- six.b('\x1a\x0d\x00\x00\x00\x01\x02\x07value'))
-
- self.packet.clear()
- self.packet[1] = [six.b('one'), six.b('two'), six.b('three')]
- self.assertEqual(self.packet._PktEncodeAttributes(),
- six.b('\x01\x05one\x01\x05two\x01\x07three'))
-
- self.packet.clear()
- self.packet[1] = [six.b('value')]
- self.packet[(1, 2)] = [six.b('value')]
- self.assertEqual(
- self.packet._PktEncodeAttributes(),
- six.b('\x1a\x0d\x00\x00\x00\x01\x02\x07value\x01\x07value'))
-
- def testPktDecodeVendorAttribute(self):
- decode = self.packet._PktDecodeVendorAttribute
-
- # Non-RFC2865 recommended form
- self.assertEqual(decode(six.b('')), [(26, six.b(''))])
- self.assertEqual(decode(six.b('12345')), [(26, six.b('12345'))])
-
- # Almost RFC2865 recommended form: bad length value
- self.assertEqual(
- decode(six.b('\x00\x00\x00\x01\x02\x06value')),
- [(26, six.b('\x00\x00\x00\x01\x02\x06value'))])
-
- # Proper RFC2865 recommended form
- self.assertEqual(
- decode(six.b('\x00\x00\x00\x01\x02\x07value')),
- [((1, 2), six.b('value'))])
-
- def testDecodePacketWithEmptyPacket(self):
- try:
- self.packet.DecodePacket(six.b(''))
- except packet.PacketError as e:
- self.failUnless('header is corrupt' in str(e))
- else:
- self.fail()
-
- def testDecodePacketWithInvalidLength(self):
- try:
- self.packet.DecodePacket(six.b('\x00\x00\x00\x001234567890123456'))
- except packet.PacketError as e:
- self.failUnless('invalid length' in str(e))
- else:
- self.fail()
-
- def testDecodePacketWithTooBigPacket(self):
- try:
- self.packet.DecodePacket(six.b('\x00\x00\x24\x00') + (0x2400 - 4) * six.b('X'))
- except packet.PacketError as e:
- self.failUnless('too long' in str(e))
- else:
- self.fail()
-
- def testDecodePacketWithPartialAttributes(self):
- try:
- self.packet.DecodePacket(
- six.b('\x01\x02\x00\x151234567890123456\x00'))
- except packet.PacketError as e:
- self.failUnless('header is corrupt' in str(e))
- else:
- self.fail()
-
- def testDecodePacketWithoutAttributes(self):
- self.packet.DecodePacket(six.b('\x01\x02\x00\x141234567890123456'))
- self.assertEqual(self.packet.code, 1)
- self.assertEqual(self.packet.id, 2)
- self.assertEqual(self.packet.authenticator, six.b('1234567890123456'))
- self.assertEqual(self.packet.keys(), [])
-
- def testDecodePacketWithBadAttribute(self):
- try:
- self.packet.DecodePacket(
- six.b('\x01\x02\x00\x161234567890123456\x00\x01'))
- except packet.PacketError as e:
- self.failUnless('too small' in str(e))
- else:
- self.fail()
-
- def testDecodePacketWithEmptyAttribute(self):
- self.packet.DecodePacket(
- six.b('\x01\x02\x00\x161234567890123456\x00\x02'))
- self.assertEqual(self.packet[0], [six.b('')])
-
- def testDecodePacketWithAttribute(self):
- self.packet.DecodePacket(
- six.b('\x01\x02\x00\x1b1234567890123456\x00\x07value'))
- self.assertEqual(self.packet[0], [six.b('value')])
-
- def testDecodePacketWithMultiValuedAttribute(self):
- self.packet.DecodePacket(
- six.b('\x01\x02\x00\x1e1234567890123456\x00\x05one\x00\x05two'))
- self.assertEqual(self.packet[0], [six.b('one'), six.b('two')])
-
- def testDecodePacketWithTwoAttributes(self):
- self.packet.DecodePacket(
- six.b('\x01\x02\x00\x1e1234567890123456\x00\x05one\x01\x05two'))
- self.assertEqual(self.packet[0], [six.b('one')])
- self.assertEqual(self.packet[1], [six.b('two')])
-
- def testDecodePacketWithVendorAttribute(self):
- self.packet.DecodePacket(
- six.b('\x01\x02\x00\x1b1234567890123456\x1a\x07value'))
- self.assertEqual(self.packet[26], [six.b('value')])
-
- def testEncodeKeyValues(self):
- self.assertEqual(self.packet._EncodeKeyValues(1, '1234'), (1, '1234'))
-
- def testEncodeKey(self):
- self.assertEqual(self.packet._EncodeKey(1), 1)
-
- def testAddAttribute(self):
- self.packet.AddAttribute(1, 1)
- self.assertEqual(dict.__getitem__(self.packet, 1), [1])
- self.packet.AddAttribute(1, 1)
- self.assertEqual(dict.__getitem__(self.packet, 1), [1, 1])
- self.packet.AddAttribute(1, [2, 3])
- self.assertEqual(dict.__getitem__(self.packet, 1), [1, 1, 2, 3])
-
-class AuthPacketConstructionTests(PacketConstructionTests):
- klass = packet.AuthPacket
-
- def testConstructorDefaults(self):
- pkt = self.klass()
- self.assertEqual(pkt.code, packet.AccessRequest)
-
-
-class AuthPacketTests(unittest.TestCase):
- def setUp(self):
- self.path = os.path.join(home, 'tests', 'data')
- self.dict = Dictionary(os.path.join(self.path, 'full'))
- self.packet = packet.AuthPacket(id=0, secret=six.b('secret'),
- authenticator=six.b('01234567890ABCDEF'), dict=self.dict)
-
- def testCreateReply(self):
- reply = self.packet.CreateReply(Test_Integer=10)
- self.assertEqual(reply.code, packet.AccessAccept)
- self.assertEqual(reply.id, self.packet.id)
- self.assertEqual(reply.secret, self.packet.secret)
- self.assertEqual(reply.authenticator, self.packet.authenticator)
- self.assertEqual(reply['Test-Integer'], [10])
-
- def testRequestPacket(self):
- self.assertEqual(self.packet.RequestPacket(),
- six.b('\x01\x00\x00\x1401234567890ABCDE'))
-
- def testRequestPacketCreatesAuthenticator(self):
- self.packet.authenticator = None
- self.packet.RequestPacket()
- self.failUnless(self.packet.authenticator is not None)
-
- def testRequestPacketCreatesID(self):
- self.packet.id = None
- self.packet.RequestPacket()
- self.failUnless(self.packet.id is not None)
-
- def testPwCryptEmptyPassword(self):
- self.assertEqual(self.packet.PwCrypt(''), six.b(''))
-
- def testPwCryptPassword(self):
- self.assertEqual(self.packet.PwCrypt('Simplon'),
- six.b('\xd3U;\xb23\r\x11\xba\x07\xe3\xa8*\xa8x\x14\x01'))
-
- def testPwCryptSetsAuthenticator(self):
- self.packet.authenticator = None
- self.packet.PwCrypt(six.u(''))
- self.failUnless(self.packet.authenticator is not None)
-
- def testPwDecryptEmptyPassword(self):
- self.assertEqual(self.packet.PwDecrypt(six.b('')), six.u(''))
-
- def testPwDecryptPassword(self):
- self.assertEqual(self.packet.PwDecrypt(
- six.b('\xd3U;\xb23\r\x11\xba\x07\xe3\xa8*\xa8x\x14\x01')),
- six.u('Simplon'))
-
-
-class AcctPacketConstructionTests(PacketConstructionTests):
- klass = packet.AcctPacket
-
- def testConstructorDefaults(self):
- pkt = self.klass()
- self.assertEqual(pkt.code, packet.AccountingRequest)
-
- def testConstructorRawPacket(self):
- raw = six.b('\x00\x00\x00\x14\xb0\x5e\x4b\xfb\xcc\x1c' \
- '\x8c\x8e\xc4\x72\xac\xea\x87\x45\x63\xa7')
- pkt = self.klass(packet=raw)
- self.assertEqual(pkt.raw_packet, raw)
-
-
-class AcctPacketTests(unittest.TestCase):
- def setUp(self):
- self.path = os.path.join(home, 'tests', 'data')
- self.dict = Dictionary(os.path.join(self.path, 'full'))
- self.packet = packet.AcctPacket(id=0, secret=six.b('secret'),
- authenticator=six.b('01234567890ABCDEF'), dict=self.dict)
-
- def testCreateReply(self):
- reply = self.packet.CreateReply(Test_Integer=10)
- self.assertEqual(reply.code, packet.AccountingResponse)
- self.assertEqual(reply.id, self.packet.id)
- self.assertEqual(reply.secret, self.packet.secret)
- self.assertEqual(reply.authenticator, self.packet.authenticator)
- self.assertEqual(reply['Test-Integer'], [10])
-
- def testVerifyAcctRequest(self):
- rawpacket = self.packet.RequestPacket()
- pkt = packet.AcctPacket(secret=six.b('secret'), packet=rawpacket)
- self.assertEqual(pkt.VerifyAcctRequest(), True)
-
- pkt.secret = six.b('different')
- self.assertEqual(pkt.VerifyAcctRequest(), False)
- pkt.secret = six.b('secret')
-
- pkt.raw_packet = six.b('X') + pkt.raw_packet[1:]
- self.assertEqual(pkt.VerifyAcctRequest(), False)
-
- def testRequestPacket(self):
- self.assertEqual(self.packet.RequestPacket(),
- six.b('\x04\x00\x00\x14\x95\xdf\x90\xccbn\xfb\x15G!\x13\xea\xfa>6\x0f'))
-
- def testRequestPacketSetsId(self):
- self.packet.id = None
- self.packet.RequestPacket()
- self.failUnless(self.packet.id is not None)
diff --git a/pyrad/tests/testProxy.py b/pyrad/tests/testProxy.py
deleted file mode 100644
index 946cd4f..0000000
--- a/pyrad/tests/testProxy.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import select
-import socket
-import unittest
-from pyrad.proxy import Proxy
-from pyrad.packet import AccessAccept
-from pyrad.packet import AccessRequest
-from pyrad.server import ServerPacketError
-from pyrad.server import Server
-from pyrad.tests.mock import MockFd
-from pyrad.tests.mock import MockPoll
-from pyrad.tests.mock import MockSocket
-from pyrad.tests.mock import MockClassMethod
-from pyrad.tests.mock import UnmockClassMethods
-
-
-class TrivialObject:
- """dummy object"""
-
-
-class SocketTests(unittest.TestCase):
- def setUp(self):
- self.orgsocket = socket.socket
- socket.socket = MockSocket
- self.proxy = Proxy()
- self.proxy._fdmap = {}
-
- def tearDown(self):
- socket.socket = self.orgsocket
-
- def testProxyFd(self):
- self.proxy._poll = MockPoll()
- self.proxy._PrepareSockets()
- self.failUnless(isinstance(self.proxy._proxyfd, MockSocket))
- self.assertEqual(list(self.proxy._fdmap.keys()), [1])
- self.assertEqual(self.proxy._poll.registry,
- [(1, select.POLLIN | select.POLLPRI | select.POLLERR)])
-
-
-class ProxyPacketHandlingTests(unittest.TestCase):
- def setUp(self):
- self.proxy = Proxy()
- self.proxy.hosts['host'] = TrivialObject()
- self.proxy.hosts['host'].secret = 'supersecret'
- self.packet = TrivialObject()
- self.packet.code = AccessAccept
- self.packet.source = ('host', 'port')
-
- def testHandleProxyPacketUnknownHost(self):
- self.packet.source = ('stranger', 'port')
- try:
- self.proxy._HandleProxyPacket(self.packet)
- except ServerPacketError as e:
- self.failUnless('unknown host' in str(e))
- else:
- self.fail()
-
- def testHandleProxyPacketSetsSecret(self):
- self.proxy._HandleProxyPacket(self.packet)
- self.assertEqual(self.packet.secret, 'supersecret')
-
- def testHandleProxyPacketHandlesWrongPacket(self):
- self.packet.code = AccessRequest
- try:
- self.proxy._HandleProxyPacket(self.packet)
- except ServerPacketError as e:
- self.failUnless('non-response' in str(e))
- else:
- self.fail()
-
-
-class OtherTests(unittest.TestCase):
- def setUp(self):
- self.proxy = Proxy()
- self.proxy._proxyfd = MockFd()
-
- def tearDown(self):
- UnmockClassMethods(Proxy)
- UnmockClassMethods(Server)
-
- def testProcessInputNonProxyPort(self):
- fd = MockFd(fd=111)
- MockClassMethod(Server, '_ProcessInput')
- self.proxy._ProcessInput(fd)
- self.assertEqual(self.proxy.called,
- [('_ProcessInput', (fd,), {})])
-
- def testProcessInput(self):
- MockClassMethod(Proxy, '_GrabPacket')
- MockClassMethod(Proxy, '_HandleProxyPacket')
- self.proxy._ProcessInput(self.proxy._proxyfd)
- self.assertEqual([x[0] for x in self.proxy.called],
- ['_GrabPacket', '_HandleProxyPacket'])
-
-
-if not hasattr(select, 'poll'):
- del SocketTests
diff --git a/pyrad/tests/testServer.py b/pyrad/tests/testServer.py
deleted file mode 100644
index e1c3c68..0000000
--- a/pyrad/tests/testServer.py
+++ /dev/null
@@ -1,311 +0,0 @@
-import select
-import socket
-import unittest
-from pyrad.packet import PacketError
-from pyrad.server import RemoteHost
-from pyrad.server import Server
-from pyrad.server import ServerPacketError
-from pyrad.tests.mock import MockFinished
-from pyrad.tests.mock import MockFd
-from pyrad.tests.mock import MockPoll
-from pyrad.tests.mock import MockSocket
-from pyrad.tests.mock import MockClassMethod
-from pyrad.tests.mock import UnmockClassMethods
-from pyrad.packet import AccessRequest
-from pyrad.packet import AccountingRequest
-
-
-class TrivialObject:
- """dummy objec"""
-
-
-class RemoteHostTests(unittest.TestCase):
- def testSimpleConstruction(self):
- host = RemoteHost('address', 'secret', 'name', 'authport', 'acctport', 'coaport')
- self.assertEqual(host.address, 'address')
- self.assertEqual(host.secret, 'secret')
- self.assertEqual(host.name, 'name')
- self.assertEqual(host.authport, 'authport')
- self.assertEqual(host.acctport, 'acctport')
- self.assertEqual(host.coaport, 'coaport')
-
- def testNamedConstruction(self):
- host = RemoteHost(address='address', secret='secret', name='name',
- authport='authport', acctport='acctport', coaport='coaport')
- self.assertEqual(host.address, 'address')
- self.assertEqual(host.secret, 'secret')
- self.assertEqual(host.name, 'name')
- self.assertEqual(host.authport, 'authport')
- self.assertEqual(host.acctport, 'acctport')
- self.assertEqual(host.coaport, 'coaport')
-
-class ServerConstructiontests(unittest.TestCase):
- def testSimpleConstruction(self):
- server = Server()
- self.assertEqual(server.authfds, [])
- self.assertEqual(server.acctfds, [])
- self.assertEqual(server.authport, 1812)
- self.assertEqual(server.acctport, 1813)
- self.assertEqual(server.coaport, 3799)
- self.assertEqual(server.hosts, {})
-
- def testParameterOrder(self):
- server = Server([], 'authport', 'acctport', 'coaport','hosts', 'dict')
- self.assertEqual(server.authfds, [])
- self.assertEqual(server.acctfds, [])
- self.assertEqual(server.authport, 'authport')
- self.assertEqual(server.acctport, 'acctport')
- self.assertEqual(server.coaport, 'coaport')
- self.assertEqual(server.dict, 'dict')
-
- def testBindDuringConstruction(self):
- def BindToAddress(self, addr):
- self.bound.append(addr)
- bta = Server.BindToAddress
- Server.BindToAddress = BindToAddress
-
- Server.bound = []
- server = Server(['one', 'two', 'three'])
- self.assertEqual(server.bound, ['one', 'two', 'three'])
- del Server.bound
-
- Server.BindToAddress = bta
-
-
-class SocketTests(unittest.TestCase):
- def setUp(self):
- self.orgsocket = socket.socket
- socket.socket = MockSocket
- self.server = Server()
-
- def tearDown(self):
- socket.socket = self.orgsocket
-
- def testBind(self):
- self.server.BindToAddress('192.168.13.13')
- self.assertEqual(len(self.server.authfds), 1)
- self.assertEqual(self.server.authfds[0].address,
- ('192.168.13.13', 1812))
-
- self.assertEqual(len(self.server.acctfds), 1)
- self.assertEqual(self.server.acctfds[0].address,
- ('192.168.13.13', 1813))
-
- def testGrabPacket(self):
- def gen(data):
- res = TrivialObject()
- res.data = data
- return res
-
- fd = MockFd()
- fd.source = object()
- pkt = self.server._GrabPacket(gen, fd)
- self.failUnless(isinstance(pkt, TrivialObject))
- self.failUnless(pkt.fd is fd)
- self.failUnless(pkt.source is fd.source)
- self.failUnless(pkt.data is fd.data)
-
- def testPrepareSocketNoFds(self):
- self.server._poll = MockPoll()
- self.server._PrepareSockets()
-
- self.assertEqual(self.server._poll.registry, [])
- self.assertEqual(self.server._realauthfds, [])
- self.assertEqual(self.server._realacctfds, [])
-
- def testPrepareSocketAuthFds(self):
- self.server._poll = MockPoll()
- self.server._fdmap = {}
- self.server.authfds = [MockFd(12), MockFd(14)]
- self.server._PrepareSockets()
-
- self.assertEqual(list(self.server._fdmap.keys()), [12, 14])
- self.assertEqual(self.server._poll.registry,
- [(12, select.POLLIN | select.POLLPRI | select.POLLERR),
- (14, select.POLLIN | select.POLLPRI | select.POLLERR)])
-
- def testPrepareSocketAcctFds(self):
- self.server._poll = MockPoll()
- self.server._fdmap = {}
- self.server.acctfds = [MockFd(12), MockFd(14)]
- self.server._PrepareSockets()
-
- self.assertEqual(list(self.server._fdmap.keys()), [12, 14])
- self.assertEqual(self.server._poll.registry,
- [(12, select.POLLIN | select.POLLPRI | select.POLLERR),
- (14, select.POLLIN | select.POLLPRI | select.POLLERR)])
-
-
-class AuthPacketHandlingTests(unittest.TestCase):
- def setUp(self):
- self.server = Server()
- self.server.hosts['host'] = TrivialObject()
- self.server.hosts['host'].secret = 'supersecret'
- self.packet = TrivialObject()
- self.packet.code = AccessRequest
- self.packet.source = ('host', 'port')
-
- def testHandleAuthPacketUnknownHost(self):
- self.packet.source = ('stranger', 'port')
- try:
- self.server._HandleAuthPacket(self.packet)
- except ServerPacketError as e:
- self.failUnless('unknown host' in str(e))
- else:
- self.fail()
-
- def testHandleAuthPacketWrongPort(self):
- self.packet.code = AccountingRequest
- try:
- self.server._HandleAuthPacket(self.packet)
- except ServerPacketError as e:
- self.failUnless('port' in str(e))
- else:
- self.fail()
-
- def testHandleAuthPacket(self):
- def HandleAuthPacket(self, pkt):
- self.handled = pkt
- hap = Server.HandleAuthPacket
- Server.HandleAuthPacket = HandleAuthPacket
-
- self.server._HandleAuthPacket(self.packet)
- self.failUnless(self.server.handled is self.packet)
-
- Server.HandleAuthPacket = hap
-
-
-class AcctPacketHandlingTests(unittest.TestCase):
- def setUp(self):
- self.server = Server()
- self.server.hosts['host'] = TrivialObject()
- self.server.hosts['host'].secret = 'supersecret'
- self.packet = TrivialObject()
- self.packet.code = AccountingRequest
- self.packet.source = ('host', 'port')
-
- def testHandleAcctPacketUnknownHost(self):
- self.packet.source = ('stranger', 'port')
- try:
- self.server._HandleAcctPacket(self.packet)
- except ServerPacketError as e:
- self.failUnless('unknown host' in str(e))
- else:
- self.fail()
-
- def testHandleAcctPacketWrongPort(self):
- self.packet.code = AccessRequest
- try:
- self.server._HandleAcctPacket(self.packet)
- except ServerPacketError as e:
- self.failUnless('port' in str(e))
- else:
- self.fail()
-
- def testHandleAcctPacket(self):
- def HandleAcctPacket(self, pkt):
- self.handled = pkt
- hap = Server.HandleAcctPacket
- Server.HandleAcctPacket = HandleAcctPacket
-
- self.server._HandleAcctPacket(self.packet)
- self.failUnless(self.server.handled is self.packet)
-
- Server.HandleAcctPacket = hap
-
-
-class OtherTests(unittest.TestCase):
- def setUp(self):
- self.server = Server()
-
- def tearDown(self):
- UnmockClassMethods(Server)
-
- def testCreateReplyPacket(self):
- class TrivialPacket:
- source = object()
-
- def CreateReply(self, **kw):
- reply = TrivialObject()
- reply.kw = kw
- return reply
-
- reply = self.server.CreateReplyPacket(TrivialPacket(),
- one='one', two='two')
- self.failUnless(isinstance(reply, TrivialObject))
- self.failUnless(reply.source is TrivialPacket.source)
- self.assertEqual(reply.kw, dict(one='one', two='two'))
-
- def testAuthProcessInput(self):
- fd = MockFd(1)
- self.server._realauthfds = [1]
- MockClassMethod(Server, '_GrabPacket')
- MockClassMethod(Server, '_HandleAuthPacket')
-
- self.server._ProcessInput(fd)
- self.assertEqual([x[0] for x in self.server.called],
- ['_GrabPacket', '_HandleAuthPacket'])
- self.assertEqual(self.server.called[0][1][1], fd)
-
- def testAcctProcessInput(self):
- fd = MockFd(1)
- self.server._realauthfds = []
- self.server._realacctfds = [1]
- MockClassMethod(Server, '_GrabPacket')
- MockClassMethod(Server, '_HandleAcctPacket')
-
- self.server._ProcessInput(fd)
- self.assertEqual([x[0] for x in self.server.called],
- ['_GrabPacket', '_HandleAcctPacket'])
- self.assertEqual(self.server.called[0][1][1], fd)
-
-
-class ServerRunTests(unittest.TestCase):
- def setUp(self):
- self.server = Server()
- self.origpoll = select.poll
- select.poll = MockPoll
-
- def tearDown(self):
- MockPoll.results = []
- select.poll = self.origpoll
- UnmockClassMethods(Server)
-
- def testRunInitializes(self):
- MockClassMethod(Server, '_PrepareSockets')
- self.assertRaises(MockFinished, self.server.Run)
- self.assertEqual(self.server.called, [('_PrepareSockets', (), {})])
- self.failUnless(isinstance(self.server._fdmap, dict))
- self.failUnless(isinstance(self.server._poll, MockPoll))
-
- def testRunIgnoresPollErrors(self):
- self.server.authfds = [MockFd()]
- MockPoll.results = [(0, select.POLLERR)]
- self.assertRaises(MockFinished, self.server.Run)
-
- def testRunIgnoresServerPacketErrors(self):
- def RaisePacketError(self, fd):
- raise ServerPacketError
- MockClassMethod(Server, '_ProcessInput', RaisePacketError)
- self.server.authfds = [MockFd()]
- MockPoll.results = [(0, select.POLLIN)]
- self.assertRaises(MockFinished, self.server.Run)
-
- def testRunIgnoresPacketErrors(self):
- def RaisePacketError(self, fd):
- raise PacketError
- MockClassMethod(Server, '_ProcessInput', RaisePacketError)
- self.server.authfds = [MockFd()]
- MockPoll.results = [(0, select.POLLIN)]
- self.assertRaises(MockFinished, self.server.Run)
-
- def testRunRunsProcessInput(self):
- MockClassMethod(Server, '_ProcessInput')
- self.server.authfds = fd = [MockFd()]
- MockPoll.results = [(0, select.POLLIN)]
- self.assertRaises(MockFinished, self.server.Run)
- self.assertEqual(self.server.called, [('_ProcessInput', (fd[0],), {})])
-
-if not hasattr(select, 'poll'):
- del SocketTests
- del ServerRunTests
diff --git a/pyrad/tests/testTools.py b/pyrad/tests/testTools.py
deleted file mode 100644
index 581ef53..0000000
--- a/pyrad/tests/testTools.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from netaddr import AddrFormatError
-from pyrad import tools
-import unittest
-import six
-import sys
-
-
-
-class EncodingTests(unittest.TestCase):
- def testStringEncoding(self):
- self.assertRaises(ValueError, tools.EncodeString, 'x' * 254)
- self.assertEqual(
- tools.EncodeString('1234567890'),
- six.b('1234567890'))
-
- def testInvalidStringEncodingRaisesTypeError(self):
- self.assertRaises(TypeError, tools.EncodeString, 1)
-
- def testAddressEncoding(self):
- self.assertRaises(AddrFormatError, tools.EncodeAddress, 'TEST123')
- self.assertEqual(
- tools.EncodeAddress('192.168.0.255'),
- six.b('\xc0\xa8\x00\xff'))
-
- def testInvalidAddressEncodingRaisesTypeError(self):
- self.assertRaises(TypeError, tools.EncodeAddress, 1)
-
- def testIntegerEncoding(self):
- self.assertEqual(tools.EncodeInteger(0x01020304), six.b('\x01\x02\x03\x04'))
-
- def testUnsignedIntegerEncoding(self):
- self.assertEqual(tools.EncodeInteger(0xFFFFFFFF), six.b('\xff\xff\xff\xff'))
-
- def testInvalidIntegerEncodingRaisesTypeError(self):
- self.assertRaises(TypeError, tools.EncodeInteger, 'ONE')
-
- def testDateEncoding(self):
- self.assertEqual(tools.EncodeDate(0x01020304), six.b('\x01\x02\x03\x04'))
-
- def testInvalidDataEncodingRaisesTypeError(self):
- self.assertRaises(TypeError, tools.EncodeDate, '1')
-
- def testEncodeAscendBinary(self):
- self.assertEqual(
- tools.EncodeAscendBinary('family=ipv4 action=discard direction=in dst=10.10.255.254/32'),
- six.b('\x01\x00\x01\x00\x00\x00\x00\x00\n\n\xff\xfe\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'))
-
- def testStringDecoding(self):
- self.assertEqual(
- tools.DecodeString(six.b('1234567890')),
- '1234567890')
-
- def testAddressDecoding(self):
- self.assertEqual(
- tools.DecodeAddress(six.b('\xc0\xa8\x00\xff')),
- '192.168.0.255')
-
- def testIntegerDecoding(self):
- self.assertEqual(
- tools.DecodeInteger(six.b('\x01\x02\x03\x04')),
- 0x01020304)
-
- def testDateDecoding(self):
- self.assertEqual(
- tools.DecodeDate(six.b('\x01\x02\x03\x04')),
- 0x01020304)
-
- def testUnknownTypeEncoding(self):
- self.assertRaises(ValueError, tools.EncodeAttr, 'unknown', None)
-
- def testUnknownTypeDecoding(self):
- self.assertRaises(ValueError, tools.DecodeAttr, 'unknown', None)
-
- def testEncodeFunction(self):
- self.assertEqual(
- tools.EncodeAttr('string', six.u('string')),
- six.b('string'))
- self.assertEqual(
- tools.EncodeAttr('octets', six.b('string')),
- six.b('string'))
- self.assertEqual(
- tools.EncodeAttr('ipaddr', '192.168.0.255'),
- six.b('\xc0\xa8\x00\xff'))
- self.assertEqual(
- tools.EncodeAttr('integer', 0x01020304),
- six.b('\x01\x02\x03\x04'))
- self.assertEqual(
- tools.EncodeAttr('date', 0x01020304),
- six.b('\x01\x02\x03\x04'))
-
- def testDecodeFunction(self):
- self.assertEqual(
- tools.DecodeAttr('string', six.b('string')),
- six.u('string'))
- self.assertEqual(
- tools.EncodeAttr('octets', six.b('string')),
- six.b('string'))
- self.assertEqual(
- tools.DecodeAttr('ipaddr', six.b('\xc0\xa8\x00\xff')),
- '192.168.0.255')
- self.assertEqual(
- tools.DecodeAttr('integer', six.b('\x01\x02\x03\x04')),
- 0x01020304)
- self.assertEqual(
- tools.DecodeAttr('date', six.b('\x01\x02\x03\x04')),
- 0x01020304)
diff --git a/pyrad/tools.py b/pyrad/tools.py
index e3ec5a8..3ddb33e 100644
--- a/pyrad/tools.py
+++ b/pyrad/tools.py
@@ -110,11 +110,10 @@ def EncodeAscendBinary(str):
terms[key] = struct.pack('B', int(value))
trailer = 8 * b'\x00'
- result = b'%s%s%s\x00%s%s%s%s%s\x00%s%s%s%s\x00\x00%s' % (
- terms['family'], terms['action'], terms['direction'], terms['src'],
- terms['dst'], terms['srcl'], terms['dstl'], terms['proto'],
- terms['sport'], terms['dport'], terms['sportq'], terms['dportq'],
- trailer)
+
+ result = b''.join((terms['family'], terms['action'], terms['direction'], b'\x00',
+ terms['src'], terms['dst'], terms['srcl'], terms['dstl'], terms['proto'], b'\x00',
+ terms['sport'], terms['dport'], terms['sportq'], terms['dportq'], b'\x00\x00', trailer))
return result
@@ -125,6 +124,12 @@ def EncodeInteger(num, format='!I'):
raise TypeError('Can not encode non-integer as integer')
return struct.pack(format, num)
+def EncodeInteger64(num, format='!Q'):
+ try:
+ num = int(num)
+ except:
+ raise TypeError('Can not encode non-integer as integer64')
+ return struct.pack(format, num)
def EncodeDate(num):
if not isinstance(num, int):
@@ -149,13 +154,13 @@ def DecodeAddress(addr):
def DecodeIPv6Prefix(addr):
addr = addr + b'\x00' * (18-len(addr))
- _, length, prefix = ':'.join(map('{:x}'.format, struct.unpack('!BB'+'H'*8, addr))).split(":", 2)
+ _, length, prefix = ':'.join(map('{0:x}'.format, struct.unpack('!BB'+'H'*8, addr))).split(":", 2)
return str(IPNetwork("%s/%s" % (prefix, int(length, 16))))
def DecodeIPv6Address(addr):
addr = addr + b'\x00' * (16-len(addr))
- prefix = ':'.join(map('{:x}'.format, struct.unpack('!'+'H'*8, addr)))
+ prefix = ':'.join(map('{0:x}'.format, struct.unpack('!'+'H'*8, addr)))
return str(IPAddress(prefix))
@@ -166,6 +171,8 @@ def DecodeAscendBinary(str):
def DecodeInteger(num, format='!I'):
return (struct.unpack(format, num))[0]
+def DecodeInteger64(num, format='!Q'):
+ return (struct.unpack(format, num))[0]
def DecodeDate(num):
return (struct.unpack('!I', num))[0]
@@ -194,6 +201,8 @@ def EncodeAttr(datatype, value):
return EncodeInteger(value, '!B')
elif datatype == 'date':
return EncodeDate(value)
+ elif datatype == 'integer64':
+ return EncodeInteger64(value)
else:
raise ValueError('Unknown attribute type %s' % datatype)
@@ -221,5 +230,7 @@ def DecodeAttr(datatype, value):
return DecodeInteger(value, '!B')
elif datatype == 'date':
return DecodeDate(value)
+ elif datatype == 'integer64':
+ return DecodeInteger64(value)
else:
raise ValueError('Unknown attribute type %s' % datatype)
diff --git a/setup.cfg b/setup.cfg
index 5cf9b0c..45e88ab 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,8 @@
-[nosetests]
-nocapture=1
-where=pyrad/tests
+[flake8]
+max-line-length = 100
+doctests = True
+
+[egg_info]
+tag_build =
+tag_date = 0
+
diff --git a/setup.py b/setup.py
old mode 100755
new mode 100644
index 2de6266..ba93ffb
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,28 @@
-#!/usr/bin/python
+#!/usr/bin/env python
from setuptools import setup, find_packages
-
-version = '2.1'
-
+import pyrad
setup(name='pyrad',
- version=version,
- author='Wichert Akkerman',
- author_email='wichert@wiggy.net',
- url='https://github.com/wichert/pyrad',
+ version=pyrad.__version__,
+ author='Istvan Ruzman, Christian Giese',
+ author_email='istvan@ruzman.eu, developer@gicnet.de',
+ url='https://github.com/pyradius/pyrad',
license='BSD',
description='RADIUS tools',
long_description=open('README.rst').read(),
classifiers=[
- 'Development Status :: 6 - Mature',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: BSD License',
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3.2',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- 'Topic :: System :: Systems Administration :: Authentication/Directory',
- ],
+ 'Development Status :: 6 - Mature',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Topic :: System :: Systems Administration :: Authentication/Directory',
+ ],
packages=find_packages(exclude=['tests']),
keywords=['radius', 'authentication'],
zip_safe=True,
@@ -31,4 +30,4 @@ setup(name='pyrad',
install_requires=['six', 'netaddr'],
tests_require='nose>=0.10.0b1',
test_suite='nose.collector',
- )
+ )
\ No newline at end of file
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/top_level.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/client_async.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/server_async.py
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/top_level.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/__init__.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/mock.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testBidict.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testClient.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testDictionary.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testHost.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testPacket.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testProxy.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testServer.py -rw-r--r-- root/root /usr/lib/python3/dist-packages/pyrad/tests/testTools.py -rw-r--r-- root/root /usr/share/doc/python3-pyrad/changelog.gz
No differences were encountered in the control files