1<?php
2/**
3 * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (LGPL). If you did
6 * not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @author   Michael Slusarz <slusarz@horde.org>
9 * @category Horde
10 * @license  http://www.horde.org/licenses/lgpl21 LGPL-2.1
11 * @package  Auth
12 */
13
14/**
15 * The Horde_Auth_Radius class provides a RADIUS implementation of the Horde
16 * authentication system.
17 *
18 * This class requires the 'radius' PECL extension:
19 *   http://pecl.php.net/package/radius
20 *
21 * @author    Michael Slusarz <slusarz@horde.org>
22 * @category  Horde
23 * @copyright 2002-2017 Horde LLC
24 * @license   http://www.horde.org/licenses/lgpl21 LGPL-2.1
25 * @package   Auth
26 */
27class Horde_Auth_Radius extends Horde_Auth_Base
28{
29    /**
30     * Constructor.
31     *
32     * @param array $params  Connection parameters.
33     * <pre>
34     * 'host' - (string) [REQUIRED] The RADIUS host to use (IP address or
35     *          fully qualified hostname).
36     * 'method' - (string) [REQUIRED] The RADIUS method to use for validating
37     *            the request.
38     *            Either: 'PAP', 'CHAP_MD5', 'MSCHAPv1', or 'MSCHAPv2'.
39     *            ** CURRENTLY, only 'PAP' is supported. **
40     * 'nas' - (string) The RADIUS NAS identifier to use.
41     *         DEFAULT: The value of $_SERVER['HTTP_HOST'] or, if not
42     *                  defined, then 'localhost'.
43     * 'port' - (integer) The port to use on the RADIUS server.
44     *          DEFAULT: Whatever the local system identifies as the
45     *                   'radius' UDP port
46     * 'retries' - (integer) The maximum number of repeated requests to make
47     *             before giving up.
48     *             DEFAULT: 3
49     * 'secret' - (string) [REQUIRED] The RADIUS shared secret string for the
50     *            host. The RADIUS protocol ignores all but the leading 128
51     *            bytes of the shared secret.
52     * 'suffix' - (string) The domain name to add to unqualified user names.
53     *             DEFAULT: NONE
54     * 'timeout' - (integer) The timeout for receiving replies from the server
55     *             (in seconds).
56     *             DEFAULT: 3
57     * </pre>
58     *
59     * @throws InvalidArgumentException
60     */
61    public function __construct(array $params = array())
62    {
63        if (!Horde_Util::extensionExists('radius')) {
64            throw new Horde_Auth_Exception(__CLASS__ . ': requires the radius PECL extension to be loaded.');
65        }
66
67        foreach (array('host', 'secret', 'method') as $val) {
68            if (!isset($params[$val])) {
69                throw new InvalidArgumentException('Missing ' . $val . ' parameter.');
70            }
71        }
72
73        $params = array_merge(array(
74            'nas' => (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost'),
75            'port' => 0,
76            'retries' => 3,
77            'suffix' => '',
78            'timeout' => 3
79        ), $params);
80
81        parent::__construct($params);
82    }
83
84    /**
85     * Find out if a set of login credentials are valid.
86     *
87     * @param string $username    The userId to check.
88     * @param array $credentials  An array of login credentials.
89     *                            For radius, this must contain a password
90     *                            entry.
91     *
92     * @throws Horde_Auth_Exception
93     */
94    protected function _authenticate($username, $credentials)
95    {
96        /* Password is required. */
97        if (!isset($credentials['password'])) {
98            throw new Horde_Auth_Exception('Password required for RADIUS authentication.');
99        }
100
101        $res = radius_auth_open();
102        radius_add_server($res, $this->_params['host'], $this->_params['port'], $this->_params['secret'], $this->_params['timeout'], $this->_params['retries']);
103        radius_create_request($res, RADIUS_ACCESS_REQUEST);
104        radius_put_attr($res, RADIUS_NAS_IDENTIFIER, $this->_params['nas']);
105        radius_put_attr($res, RADIUS_NAS_PORT_TYPE, RADIUS_VIRTUAL);
106        radius_put_attr($res, RADIUS_SERVICE_TYPE, RADIUS_FRAMED);
107        radius_put_attr($res, RADIUS_FRAMED_PROTOCOL, RADIUS_PPP);
108        radius_put_attr($res, RADIUS_CALLING_STATION_ID, isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : '127.0.0.1');
109
110        /* Insert username/password into request. */
111        radius_put_attr($res, RADIUS_USER_NAME, $username);
112        radius_put_attr($res, RADIUS_USER_PASSWORD, $credentials['password']);
113
114        /* Send request. */
115        $success = radius_send_request($res);
116
117        switch ($success) {
118        case RADIUS_ACCESS_ACCEPT:
119            break;
120
121        case RADIUS_ACCESS_REJECT:
122            throw new Horde_Auth_Exception('Authentication rejected by RADIUS server.');
123
124        default:
125            throw new Horde_Auth_Exception(radius_strerror($res));
126        }
127    }
128
129}
130