<?php
/*
+ ----------------------------------------------------------------------------+
|     e107 website system
|
|     Steve Dunstan 2001-2002
|     http://e107.org
|     jalist@e107.org
|
|     Released under the terms and conditions of the
|     GNU General Public License (http://gnu.org).
|
|     $Source: /cvs_backup/e107_0.8/e107_plugins/alt_auth/radius_auth.php,v $
|     $Revision: 1.1 $
|     $Date: 2008-09-02 19:39:12 $
|     $Author: e107steved $
+----------------------------------------------------------------------------+

RFC2865 is the main RADIUS standard - http://www.faqs.org/rfcs/rfc2865

Potential enhancements:
	- Multiple servers (done, but not tested)
	- Configurable port (probably not necessary)
	- Configurable timeout
	- Configurable retries

Error recfrom: 10054 - winsock error for 'connection reset'
*/

define('RADIUS_DEBUG',TRUE);
class auth_login
{

	var $server;
	var $secret;
	var $port;
	var $usr;
	var $pwd;
	var $ErrorText;
	var $connection;			// Handle to use on successful creation
	var $result;
	var $Available;

	function auth_login()
	{
		$this->copyAttribs = array();
		$sql = new db;
		$sql -> db_Select("alt_auth", "*", "auth_type = 'radius' ");
		while($row = $sql -> db_Fetch())
		{
			$radius[$row['auth_parmname']] = base64_decode(base64_decode($row['auth_parmval']));
		}
		$this->server = explode(',',$radius['radius_server']);
		$this->port = 1812;								// Assume fixed port number for now - 1812 (UDP) is listed for servers, 1645 for authentification. (1646, 1813 for accounting)
														// (A Microsoft app note says 1812 is the RFC2026-compliant port number. (http://support.microsoft.com/kb/230786)
//		$this->port = 1645;
		$this->secret = explode(',',$radius['radius_secret']);
		if ((count($this->server) > 1)  && (count($this->secret) == 1))
		{
			$this->secret = array();
			foreach ($this->server as $k => $v)
			{
				$this->secret[$k] = $radius['radius_secret'];		// Same secret for all servers, if only one entered
			}
		}
		$this->ErrorText = '';
		if(!function_exists('radius_auth_open'))
		{
			$this->Available = FALSE;
			return false;
		}

		if(!$this -> connect())
		{
			return AUTH_NOCONNECT;
		}
	}



	function makeErrorText($extra = '')
	{
		$this->ErrorText = $extra.radius_strerror($this->connection) ;
		if (!RADIUS_DEBUG) return;
		$text = "<br />Server: {$this->server}  Stored secret: ".radius_server_secret($this->connection)."  Port: {$this->port}";
		$this->ErrorText .= $text;
	}



	function connect()
	{
		// Try to connect to a radius server
		if (!($this->connection = radius_auth_open()))
		{
			$this->makeErrorText('RADIUS open failed: ') ;
			return FALSE;
		}
		foreach ($this->server as $k => $s)
		{
			if (!radius_add_server($this->connection, $s, $this->port, $this->secret[$k], 15, 1))	// fixed 15 second timeout, one try ATM
			{
				$this->makeErrorText('RADIUS add server failed: ') ;
				return FALSE;
			}
		}
		return TRUE;
	}



	function close()
	{
		if ( !radius_close( $this->connection))		// (Not strictly necessary, but tidy)
		{
			$this->makeErrorText('RADIUS close error: ') ;
			return false;
		}
		else
		{
			return true;
		}
	}



	function login($uname, $pass, &$newvals, $connect_only = FALSE)
	{
		// Create authentification request
		if (!radius_create_request($this->connection,RADIUS_ACCESS_REQUEST))
		{
			$this->makeErrorText('RADIUS failed authentification request: ') ;
			return AUTH_NOCONNECT;
		}

		if (trim($pass) == '') return AUTH_BADPASSWORD;				// Pick up a blank password - always expect one

		// Attach username and password
		if (!radius_put_attr($this->connection,RADIUS_USER_NAME,$uname)
		|| !radius_put_attr($this->connection,RADIUS_USER_PASSWORD,$pass))
		{
			$this->makeErrorText('RADIUS could not attach username/password: ') ;
			return AUTH_NOCONNECT;
		}

		// Finally, send request to server
		switch (radius_send_request($this->connection))
		{
			case RADIUS_ACCESS_ACCEPT :		// Valid username/password
				break;
			case RADIUS_ACCESS_CHALLENGE :	// CHAP response required - not currently implemented
				$this->makeErrorText('CHAP not supported');
				return AUTH_NOUSER;
			case RADIUS_ACCESS_REJECT :		// Specifically rejected
			default:						// Catch-all
				$this->makeErrorText('RADIUS validation error: ') ;
				return AUTH_NOUSER;
		}			

// User accepted here.

		if ($connect_only) return AUTH_SUCCESS;
		return AUTH_SUCCESS;					// Not interested in any attributes returned ATM, so done.



		// See if we get any attributes - not really any use to us unless we implement CHAP, so disabled ATM
		$attribs = array();
		while ($resa = radius_get_attr($this->connection)) 
		{
			if (!is_array($resa)) 
			{
				$this->makeErrorText("Error getting attribute: ");
				exit;
			}
//			Decode attribute according to type (this isn't an exhaustive list)
//		Codes: 2, 3, 4, 5, 30, 31, 32, 60, 61 should never be received by us
//		Codes 17, 21 not assigned
			switch ($resa['attr'])
			{
				case 8 :		// IP address to be set (255.255.255.254 indicates 'allocate your own address')
				case 9 :		// Subnet mask
				case 14 :		// Login-IP host
					$attribs[$resa['attr']] = radius_cvt_addr($resa['data']);
					break;
				case 6 :		// Service type  (integer bitmap)
				case 7 :		// Protocol (integer bitmap)
				case 10 :		// Routing method (integer)
				case 12 :		// Framed MTU
				case 13 :		// Compression method
				case 15 :		// Login service (bitmap)
				case 16 :		// Login TCP port
				case 23 :		// Framed IPX network (0xFFFFFFFE indicates 'allocate your own')
				case 27 :		// Session timeout - maximum connection/login time in seconds
				case 28 :		// Idle timeout in seconds
				case 29 :		// Termination action
				case 37 :		// AppleTalk link number
				case 38 :		// AppleTalk network
				case 62 :		// Max ports
				case 63 :		// Login LAT port
					$attribs[$resa['attr']] = radius_cvt_int($resa['data']);
					break;
				case 1 :		// User name
				case 11 :		// Filter ID - could get several of these
				case 18 :		// Reply message (text, various purposes)
				case 19 :		// Callback number
				case 20 :		// Callback ID
				case 22 :		// Framed route - could get several of these
				case 24 :		// State - used in CHAP
				case 25 :		// Class
				case 26 :		// Vendor-specific
				case 33 :		// Proxy State
				case 34 :		// Login LAT service
				case 35 :		// Login LAT node
				case 36 :		// Login LAT group
				case 39 :		// AppleTalk zone
				default :
					$attribs[$resa['attr']] = radius_cvt_string($resa['data']);		// Default to string type
			}
			printf("Got Attr: %d => %d Bytes %s\n", $resa['attr'], strlen($attribs[$resa['attr']]), $attribs[$resa['attr']]);
		}

		return AUTH_SUCCESS;
	}
}
?>
