| [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] | 52 | require_once('classes/Authenticator.php'); |
|---|
| 53 | require_once('classes/User.php'); |
|---|
| 54 | |
|---|
| [516] | 55 | // Including PEAR RADIUS and CHAP MD5 interface classes |
|---|
| [874] | 56 | require_once('Auth/RADIUS.php'); |
|---|
| 57 | require_once('Crypt/CHAP.php'); |
|---|
| [513] | 58 | |
|---|
| 59 | /** Internal wifidog user database authentication source */ |
|---|
| [516] | 60 | class 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 | ?> |
|---|