root/trunk/wifidog-auth/wifidog/classes/AuthenticatorRadius.php @ 877

Revision 877, 15.8 KB (checked in by fproulx, 7 years ago)

2005-12-29 Fran�ois Proulx <francois.proulx@…>

  • Added Aidan's file_exists_incpath function to Dependencies class
  • Translated and standardized most of PostgreSQL abstraction class.
  • Removed blank lines at end of many files
  • Tested the new Phlickr package, which fixes URL on Flickr
  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[513]1<?php
[807]2
[866]3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
[516]4
[866]5// +-------------------------------------------------------------------+
6// | WiFiDog Authentication Server                                     |
7// | =============================                                     |
8// |                                                                   |
9// | The WiFiDog Authentication Server is part of the WiFiDog captive  |
10// | portal suite.                                                     |
11// +-------------------------------------------------------------------+
12// | PHP version 5 required.                                           |
13// +-------------------------------------------------------------------+
14// | Homepage:     http://www.wifidog.org/                             |
15// | Source Forge: http://sourceforge.net/projects/wifidog/            |
16// +-------------------------------------------------------------------+
17// | This program is free software; you can redistribute it and/or     |
18// | modify it under the terms of the GNU General Public License as    |
19// | published by the Free Software Foundation; either version 2 of    |
20// | the License, or (at your option) any later version.               |
21// |                                                                   |
22// | This program is distributed in the hope that it will be useful,   |
23// | but WITHOUT ANY WARRANTY; without even the implied warranty of    |
24// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     |
25// | GNU General Public License for more details.                      |
26// |                                                                   |
27// | You should have received a copy of the GNU General Public License |
28// | along with this program; if not, contact:                         |
29// |                                                                   |
30// | Free Software Foundation           Voice:  +1-617-542-5942        |
31// | 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        |
32// | Boston, MA  02111-1307,  USA       gnu@gnu.org                    |
33// |                                                                   |
34// +-------------------------------------------------------------------+
35
[516]36/**
[866]37 * @package    WiFiDogAuthServer
[874]38 * @subpackage Authenticators
[866]39 * @author     Benoit Gregoire <bock@step.polymtl.ca>
40 * @author     Francois Proulx <francois.proulx@gmail.com>
[874]41 * @copyright  2005 Benoit Gregoire, Technologies Coeus inc.
42 * @copyright  2005 Francois Proulx, Technologies Coeus inc.
[866]43 * @version    CVS: $Id$
44 * @link       http://sourceforge.net/projects/wifidog/
45 */
46
47/**
[516]48 * Portions of this code are based on PEAR RADIUS Auth class examples provided
[866]49 * Copyright (c) 2003, Michael Bretterklieber <michael@bretterklieber.com>
[516]50 * All rights reserved.
51 */
[874]52require_once('classes/Authenticator.php');
53require_once('classes/User.php');
54
[516]55// Including PEAR RADIUS and CHAP MD5 interface classes
[874]56require_once('Auth/RADIUS.php');
57require_once('Crypt/CHAP.php');
[513]58
59/** Internal wifidog user database authentication source */
[516]60class AuthenticatorRadius extends Authenticator
61{
[866]62    private $mRadius_hostname;
63    private $mRadius_auth_port;
64    private $mRadius_acct_port;
65    private $mRadius_secret_key;
66    private $mRadius_encryption_method;
[513]67
[866]68    /**
69     * AuthenticatorRadius constructor
70     * Example:  new AuthenticatorRadius(IDRC_ACCOUNT_ORIGIN, "192.168.0.11",
71     * 1812, 1813, "secret_key", "CHAP_MD5");
72     * @param $account_orgin : The network ID
73     * @param $host : hostname of the RADIUS server
74     * @param $auth_port : Authentication port of the RADIUS server
75     * @param $acct_port : Accounting port of the RADIUS server
76     * @param $secret_key : The secret key between between this client and the
77     * server
78     * @param $encryption_method : The encryption method choosen for the
79     * requests
80     */
81    function __construct($account_orgin, $host = "localhost", $auth_port = 1812, $acct_port = 1813, $secret_key = "", $encryption_method = "CHAP_MD5")
82    {
83        parent :: __construct($account_orgin);
[513]84
[866]85        // Store RADIUS server parameters
86        // Defaults to localhost:0 with MD5 CHAP encryption
87        // Setting port to 0 will get default RADIUS Auth service port ( either 1645 or 1812 )
88        $this->mRadius_hostname = $host;
89        $this->mRadius_auth_port = $auth_port;
90        $this->mRadius_acct_port = $acct_port;
91        $this->mRadius_secret_key = $secret_key;
92        $this->mRadius_encryption_method = $encryption_method;
93    }
[513]94
[866]95    /** Attempts to login a user against the authentication source.  If successfull, returns a User object
96     * @param username:  A valid identifying token for the source.  Not necessarily unique.  For local user, bots username and email are valid.
97     * @param password:  Clear text password.
98     * @retval The actual User object if login was successfull, false otherwise.
99     */
100    function login($username, $password, & $errmsg = null)
101    {
102        global $db;
103        $security = new Security();
104        $retval = false;
[877]105        $username = $db->escapeString($username);
106        $password = $db->escapeString($password);
[513]107
[866]108        /*
109         * Supported encryption methods are :
110         *
111         * CHAP_MD5 :Challenge-Handshake Authentication Protocol with MD5
112         * MSCHAPv1 and MSCHAPv2 : Microsoft's CHAP implementation
113         */
114        switch ($this->mRadius_encryption_method)
115        {
116            case "PAP" :
117            case "CHAP_MD5" :
118            case "MSCHAPv1" :
119            case "MSCHAPv2" :
120                // Instanciate PEAR class
121                $classname = 'Auth_RADIUS_'.$this->mRadius_encryption_method;
122                $radius_server = new $classname ($username, $password);
123                $radius_server->addServer($this->mRadius_hostname, $this->mRadius_auth_port, $this->mRadius_secret_key);
124                break;
125            default :
126                // Invalid encryption method
127                $errmsg = _("Invalid RADIUS encryption method.");
128                return false;
129        }
[513]130
[866]131        // Instructing PEAR RADIUS class auth parameters
132        $radius_server->username = $username;
133        // Depending on the auth method, generate challenge response
134        switch ($this->mRadius_encryption_method)
135        {
136            case 'CHAP_MD5' :
137            case 'MSCHAPv1' :
138                $classname = $this->mRadius_encryption_method == 'MSCHAPv1' ? 'Crypt_CHAP_MSv1' : 'Crypt_CHAP_MD5';
139                $crypt_class = new $classname;
140                $crypt_class->password = $password;
141                $radius_server->challenge = $crypt_class->challenge;
142                $radius_server->chapid = $crypt_class->chapid;
143                $radius_server->response = $crypt_class->challengeResponse();
144                $radius_server->flags = 1;
145                break;
146            case 'MSCHAPv2' :
147                $crypt_class = new Crypt_CHAP_MSv2;
148                $crypt_class->username = $username;
149                $crypt_class->password = $password;
150                $radius_server->challenge = $crypt_class->authChallenge;
151                $radius_server->peerChallenge = $crypt_class->peerChallenge;
152                $radius_server->chapid = $crypt_class->chapid;
153                $radius_server->response = $crypt_class->challengeResponse();
154                break;
155            default :
156                $radius_server->password = $password;
157                break;
158        }
[513]159
[866]160        if (!$radius_server->start())
161        {
162            $errmsg = _("Could not initiate PEAR RADIUS Auth class : ".$radius_server->getError());
163            return false;
164        }
[516]165
[866]166        // Send the authentication request to the RADIUS server
167        $result = $radius_server->send();
[516]168
[866]169        if (PEAR :: isError($result))
170        {
171            $errmsg = _("Failed to send authentication request to the RADIUS server. : ".$result->getMessage());
172            return false;
173        }
174        else
175            if ($result === true)
176            {
177                // RADIUS authentication succeeded !
178                // Now checking for local copy of this user
179                $user_info = null;
180                $sql = "SELECT user_id, pass FROM users WHERE (username='$username') AND account_origin='".$this->getNetwork()->getId()."'";
[877]181                $db->execSqlUniqueRes($sql, $user_info, false);
[516]182
[866]183                if ($user_info != null)
184                {
185                    $user = new User($user_info['user_id']);
186                    if ($user->isUserValid($errmsg))
187                    {
188                        $retval = & $user;
189                        User :: setCurrentUser($user);
190                        $errmsg = _("Login successfull");
191                    }
192                    else
193                    {
194                        $retval = false;
195                        //Reason for refusal is already in $errmsg
196                    }
197                }
198                else
199                {
200                    // This user has been succcessfully authenticated through remote RADIUS, but it's not yet in our local database
201                    // Creating the user with a Global Unique ID, empty email and password
202                    // Local database password hashing is based on an empty string ( we do not store remote passwords )
203                    $user = User :: createUser(get_guid(), $username, $this->getNetwork(), "", "");
204                    $retval = & $user;
205                    // Validate the user right away !
206                    $user->setAccountStatus(ACCOUNT_STATUS_ALLOWED);
207                    User :: setCurrentUser($user);
208                    $errmsg = _("Login successfull");
209                }
210                return $retval;
211            }
212            else
213            {
214                $errmsg = _("The RADIUS server rejected this username/password combination.");
215                return false;
216            }
[516]217
[866]218        $radius_server->close();
219    }
[516]220
[866]221    /** Start accounting traffic for the user
222     * $conn_id:  The connection id for the connection to work on */
223    function acctStart($conn_id, & $errmsg = null)
224    {
225        global $db;
226        $info = null;
[877]227        $conn_id = $db->escapeString($conn_id);
228        $db->execSqlUniqueRes("SELECT NOW(), *, CASE WHEN ((NOW() - reg_date) > networks.validation_grace_time) THEN true ELSE false END AS validation_grace_time_expired FROM connections JOIN users ON (users.user_id=connections.user_id) JOIN networks ON (users.account_origin = networks.network_id) WHERE connections.conn_id=$conn_id", $info, false);
[807]229
[866]230        // RADIUS accounting start
231        $radius_acct = new Auth_RADIUS_Acct_Start;
232        $radius_acct->addServer($this->mRadius_hostname, $this->mRadius_acct_port, $this->mRadius_secret_key);
233        // Specify the user for which accounting will be done
234        $radius_acct->username = $info['username'];
235        // Specify the way the user has been authenticated ( via RADIUS, the class did it )
236        $radius_acct->authentic = RADIUS_AUTH_RADIUS;
237        // Set the session ID to the generated token
238        $radius_acct->session_id = $info['token'];
[516]239
[866]240        $status = $radius_acct->start();
241        if (PEAR :: isError($status))
242            return false;
[516]243
[866]244        $result = $radius_acct->send();
245        if (PEAR :: isError($result))
246        {
247            $errmsg = "Could not send accounting request to RADIUS server.";
248            return false;
249        }
250        else
251            if ($result !== true)
252            {
253                $radius_acct->close();
254                $errmsg = "Accounting request rejected by RADIUS server.";
255                return false;
256            }
[516]257
[866]258        $radius_acct->close();
[516]259
[866]260        // Run generic accounting ( local traffic counters ) only if RADIUS went OK
261        parent :: acctStart($conn_id);
262        return true;
263    }
[516]264
[866]265    /** Update traffic counters
266     * $conn_id: The connection id for the connection to work on */
267    function acctUpdate($conn_id, $incoming, $outgoing, & $errmsg = null)
268    {
269        // Call generic traffic updater ( local database )
270        parent :: acctUpdate($conn_id, $incoming, $outgoing);
271        global $db;
272        $info = null;
[877]273        $conn_id = $db->escapeString($conn_id);
274        $db->execSqlUniqueRes("SELECT NOW(), *, CASE WHEN ((NOW() - reg_date) > networks.validation_grace_time) THEN true ELSE false END AS validation_grace_time_expired FROM connections JOIN users ON (users.user_id=connections.user_id) JOIN networks ON (users.account_origin = networks.network_id) WHERE connections.conn_id=$conn_id", $info, false);
[807]275
[866]276        // RADIUS accounting ping
277        // Session is completely based on Database time
278        $session_time = strtotime($info['now']) - strtotime($info['timestamp_in']);
[516]279
[866]280        $radius_acct = new Auth_RADIUS_Acct_Update;
281        $radius_acct->addServer($this->mRadius_hostname, $this->mRadius_acct_port, $this->mRadius_secret_key);
282        // Specify the user for which accounting will be done
283        $radius_acct->username = $info['username'];
284        $racct->session_time = $session_time;
285        // Set the session ID to the generated token
286        $radius_acct->session_id = $info['token'];
[516]287
[866]288        $status = $radius_acct->start();
289        if (PEAR :: isError($status))
290            return false;
[516]291
[866]292        // Send traffic data along with the request
293        $radius_acct->putAttribute(RADIUS_ACCT_INPUT_PACKETS, $incoming);
294        $radius_acct->putAttribute(RADIUS_ACCT_OUTPUT_PACKETS, $outgoing);
[516]295
[866]296        $result = $radius_acct->send();
297        if (PEAR :: isError($result))
298        {
299            $errmsg = "Could not send accounting request to RADIUS server.";
300            return false;
301        }
302        else
303            if ($result !== true)
304            {
305                $radius_acct->close();
306                $errmsg = "Accounting request rejected by RADIUS server.";
307                return false;
308            }
[516]309
[866]310        $radius_acct->close();
311        return true;
312    }
[516]313
[866]314    /** Final update and stop accounting
315     * $conn_id:  The connection id (the token id) for the connection to work on
316     * */
317    function acctStop($conn_id, & $errmsg = null)
318    {
319        parent :: acctStop($conn_id);
320        global $db;
321        $info = null;
[877]322        $conn_id = $db->escapeString($conn_id);
323        $db->execSqlUniqueRes("SELECT NOW(), *, CASE WHEN ((NOW() - reg_date) > networks.validation_grace_time) THEN true ELSE false END AS validation_grace_time_expired FROM connections JOIN users ON (users.user_id=connections.user_id) JOIN networks ON (users.account_origin = networks.network_id) WHERE connections.conn_id=$conn_id", $info, false);
[517]324
[866]325        // RADIUS accounting stop
326        // Session is completely based on Database time
327        $session_time = strtotime($info['now']) - strtotime($info['timestamp_in']);
[516]328
[866]329        $radius_acct = new Auth_RADIUS_Acct_Stop;
330        $radius_acct->addServer($this->mRadius_hostname, $this->mRadius_acct_port, $this->mRadius_secret_key);
331        // Specify the user for which accounting will be done
332        $radius_acct->username = $info['username'];
333        $racct->session_time = $session_time;
334        // Set the session ID to the generated token
335        $radius_acct->session_id = $info['token'];
[516]336
[866]337        $status = $radius_acct->start();
338        if (PEAR :: isError($status))
339        {
340            $errmsg = "Could not initiate PEAR RADIUS class.";
341            return false;
342        }
[516]343
[866]344        // Cause of session termination
345        $radius_acct->putAttribute(RADIUS_ACCT_TERMINATE_CAUSE, RADIUS_TERM_SESSION_TIMEOUT);
346        $result = $radius_acct->send();
[517]347
[866]348        if (PEAR :: isError($result))
349        {
350            $errmsg = "Could not send accounting request to RADIUS server.";
351            return false;
352        }
353        else
354            if ($result !== true)
355            {
356                $radius_acct->close();
357                $errmsg = "Accounting request rejected by RADIUS server.";
358                return false;
359            }
[516]360
[866]361        $radius_acct->close();
362        return true;
363    }
[516]364
[866]365}
366
367/*
368 * Local variables:
369 * tab-width: 4
370 * c-basic-offset: 4
371 * c-hanging-comment-ender-p: nil
372 * End:
373 */
374
[877]375?>
Note: See TracBrowser for help on using the browser.