From 334c12cd65b573f08af4f75ab1b8299956971fb9 Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Tue, 24 Mar 2009 08:51:42 +0000
Subject: [PATCH] New authentication module: radius:Radius.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@1440 44740490-163a-0410-bde0-09ae8108e29a
---
 modules/radius/default-disable            |   0
 modules/radius/docs/radius.txt            |  86 ++++++++++
 modules/radius/lib/Auth/Source/Radius.php | 184 ++++++++++++++++++++++
 3 files changed, 270 insertions(+)
 create mode 100644 modules/radius/default-disable
 create mode 100644 modules/radius/docs/radius.txt
 create mode 100644 modules/radius/lib/Auth/Source/Radius.php

diff --git a/modules/radius/default-disable b/modules/radius/default-disable
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/radius/docs/radius.txt b/modules/radius/docs/radius.txt
new file mode 100644
index 000000000..4a7614404
--- /dev/null
+++ b/modules/radius/docs/radius.txt
@@ -0,0 +1,86 @@
+RADIUS module
+=============
+
+The RADIUS module provides a single authentication module:
+
+`radius:Radius`
+: Authenticate a user against a RADIUS server.
+
+This authentication module contacts a RADIUS server, and authenticates
+the user by using username & password authentication.
+
+To use this module, enable the radius module by creating a file named
+`enable` in the `modules/radius/`-directory. Then you need to add a
+authentication source which uses the `radius:Radius` module to
+`config/authsources.php`:
+
+    'example-radius' => array(
+        'radius:Radius',
+
+        /*
+         * The hostname of the RADIUS server.
+         * Required.
+         */
+        'hostname' => 'radius.example.org',
+
+        /*
+         * The port number of the radius server.
+         * Optional, defaults to 1812.
+         */
+        'port' => 1812,
+
+        /*
+         * The shared secret which is used when contacting the RADUIS server.
+         * Required.
+         */
+        'secret' => 'topsecret',
+
+        /*
+         * The timeout for contacting the RADIUS server, in seconds.
+         * Optional, defaults to 5 seconds.
+         */
+        'timeout' => 5,
+
+        /*
+         * The number of times we should retry connections to the RADIUS server.
+         * Optional, defaults to 3 attempts.
+         */
+        'retries' => 3,
+
+        /*
+         * The attribute name we should store the username in. Ths username
+         * will not be saved in any attribute if this is NULL.
+         * Optional, defaults to NULL.
+         */
+        'username_attribute' => 'eduPersonPrincipalName',
+    ),
+
+
+User attributes
+---------------
+
+If the RADIUS server is configured to include attributes for the user in
+the response, this module may be able to extract them. This requires the
+attributes to be stored in a vendor-specific attribute in the response
+from the RADIUS server.
+
+The code expects one vendor-attribute with a specific vendor and a specific
+vendor attribute type for each user attribute. The vendor-attribute must
+contain a value on the form <name>=<value>.
+
+The following configuration options are available for user attributes:
+
+        /*
+         * This is the vendor for the vendor-specific attribute which contains
+         * the attributes for this user. This can be NULL if no attributes are
+         * included in the response.
+         * Optional, defaults to NULL.
+         */
+        'attribute_vendor' => 23735,
+
+        /*
+         * The vendor attribute-type of the attribute which contains the
+         * attributes for the user.
+         * Required if 'vendor' is set.
+         */
+        'attribute_vendor_type' => 4,
diff --git a/modules/radius/lib/Auth/Source/Radius.php b/modules/radius/lib/Auth/Source/Radius.php
new file mode 100644
index 000000000..283027ec0
--- /dev/null
+++ b/modules/radius/lib/Auth/Source/Radius.php
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * RADIUS authentication source.
+ *
+ * This class is based on www/auth/login-radius.php.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_radius_Auth_Source_Radius extends sspmod_core_Auth_UserPassBase {
+
+	/**
+	 * The hostname of the radius server.
+	 */
+	private $hostname;
+
+	/**
+	 * The port of the radius server.
+	 */
+	private $port;
+
+	/**
+	 * The secret used when communicating with the radius server.
+	 */
+	private $secret;
+
+	/**
+	 * The timeout for contacting the radius server.
+	 */
+	private $timeout;
+
+	/**
+	 * The number of retries which should be attempted.
+	 */
+	private $retries;
+
+	/**
+	 * The attribute name where the username should be stored.
+	 */
+	private $usernameAttribute;
+
+	/**
+	 * The vendor for the RADIUS attributes we are interrested in.
+	 */
+	private $vendor;
+
+	/**
+	 * The vendor-specific attribute for the RADIUS attributes we are interrested in.
+	 */
+	private $vendorType;
+
+
+	/**
+	 * Constructor for this authentication source.
+	 *
+	 * @param array $info  Information about this authentication source.
+	 * @param array $config  Configuration.
+	 */
+	public function __construct($info, $config) {
+		assert('is_array($info)');
+		assert('is_array($config)');
+
+		/* Call the parent constructor first, as required by the interface. */
+		parent::__construct($info, $config);
+
+		/* Parse configuration. */
+		$config = SimpleSAML_Configuration::loadFromArray($config,
+			'Authentication source ' . var_export($this->authId, TRUE));
+
+		$this->hostname = $config->getString('hostname');
+		$this->port = $config->getIntegerRange('port', 1, 65535, 1812);
+		$this->secret = $config->getString('secret');
+		$this->timeout = $config->getInteger('timeout', 5);
+		$this->retries = $config->getInteger('retries', 3);
+		$this->usernameAttribute = $config->getString('username_attribute', NULL);
+
+		$this->vendor = $config->getInteger('attribute_vendor', NULL);
+		if ($this->vendor !== NULL) {
+			$this->vendorType = $config->getInteger('attribute_vendor_type');
+		}
+	}
+
+
+	/**
+	 * Attempt to log in using the given username and password.
+	 *
+	 * @param string $username  The username the user wrote.
+	 * @param string $password  The password the user wrote.
+	 * @return array  Associative array with the users attributes.
+	 */
+	protected function login($username, $password) {
+		assert('is_string($username)');
+		assert('is_string($password)');
+
+		$radius = radius_auth_open();
+		if (!radius_add_server($radius, $this->hostname, $this->port, $this->secret, $this->timeout, $this->retries)) {
+			throw new Exception('Error connecting to radius server: ' . radius_strerror($radius));
+		}
+
+		if (!radius_create_request($radius, RADIUS_ACCESS_REQUEST)) {
+			throw new Exception('Error creating radius request: ' . radius_strerror($radius));
+		}
+
+		radius_put_attr($radius, RADIUS_USER_NAME, $username);
+		radius_put_attr($radius, RADIUS_USER_PASSWORD, $password);
+
+		$res = radius_send_request($radius);
+		if ($res != RADIUS_ACCESS_ACCEPT) {
+			switch ($res) {
+			case RADIUS_ACCESS_REJECT:
+				/* Invalid username or password. */
+				throw new SimpleSAML_Error_Error('WRONGUSERPASS');
+			case RADIUS_ACCESS_CHALLENGE:
+				throw new Exception('Radius authentication error: Challenge requested, but not supported.');
+			default:
+				throw new Exception('Error during radius authentication: ' . radius_strerror($radius));
+			}
+		}
+
+		/* If we get this far, we have a valid login. */
+
+		$attributes = array();
+
+		if ($this->usernameAttribute !== NULL) {
+			$attributes[$this->usernameAttribute] = array($username);
+		}
+
+		if ($this->vendor === NULL) {
+			/*
+			 * We aren't interrested in any vendor-specific attributes. We are
+			 * therefore done now.
+			 */
+			return $attributes;
+		}
+
+		/* get AAI attribute sets. Contributed by Stefan Winter, (c) RESTENA */
+		while ($resa = radius_get_attr($radius)) {
+
+			if (!is_array($resa)) {
+				throw new Exception('Error getting radius attributes: ' . radius_strerror($radius));
+			}
+
+			if ($resa['attr'] !== RADIUS_VENDOR_SPECIFIC) {
+				continue;
+			}
+
+			$resv = radius_get_vendor_attr($resa['data']);
+			if (!is_array($resv)) {
+				throw new Exception('Error getting vendor specific attribute: ' . radius_strerror($radius));
+			}
+
+			$vendor = $resv['vendor'];
+			$attrv = $resv['attr'];
+			$datav = $resv['data'];
+
+			/*
+			 * Uncomment this to debug vendor attributes.
+			 */
+			//printf("Got Vendor Attr:%d %d Bytes %s<br/>", $attrv, strlen($datav), bin2hex($datav));
+
+			if ($vendor != $this->vendor || $attrv != $this->vendorType) {
+				continue;
+			}
+
+			$attrib_name = strtok($datav,'=');
+			$attrib_value = strtok('=');
+
+			/* if the attribute name is already in result set, add another value */
+			if (array_key_exists($attrib_name, $attributes)) {
+				$attributes[$attrib_name][] = $attrib_value;
+			} else {
+				$attributes[$attrib_name] = array($attrib_value);
+			}
+		}
+		/* end of contribution */
+
+		return $attributes;
+	}
+
+}
+
+
+?>
\ No newline at end of file
-- 
GitLab