|
1 <?php |
|
2 /**!info** |
|
3 { |
|
4 "Plugin Name" : "RADIUS authentication", |
|
5 "Plugin URI" : "http://enanocms.org/plugin/radiusauth", |
|
6 "Description" : "Allows authentication to Enano via a RADIUS server.", |
|
7 "Author" : "Dan Fuhry", |
|
8 "Version" : "1.0", |
|
9 "Author URI" : "http://enanocms.org/", |
|
10 "Auth plugin" : true |
|
11 } |
|
12 **!*/ |
|
13 |
|
14 /* |
|
15 * RADIUS authentication plugin for Enano |
|
16 * (C) 2010 Dan Fuhry |
|
17 * |
|
18 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License |
|
19 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. |
|
20 * |
|
21 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied |
|
22 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. |
|
23 * |
|
24 * Please note: the back-end RADIUS library files, libradauth.php and libmschap.php, are under the BSD license. |
|
25 */ |
|
26 |
|
27 if ( getConfig('radius_enable', 0) == 1 ) |
|
28 { |
|
29 $plugins->attachHook('login_process_userdata_json', 'return radius_auth_hook($userinfo, $req["level"], $req["remember"]);'); |
|
30 } |
|
31 |
|
32 function radius_auth_hook($userinfo, $level, $remember) |
|
33 { |
|
34 global $db, $session, $paths, $template, $plugins; // Common objects |
|
35 |
|
36 // First try to just authenticate the user in RADIUS |
|
37 require_once(ENANO_ROOT . '/plugins/radiusauth/libradauth.php'); |
|
38 |
|
39 $server = getConfig('radius_server', false); |
|
40 $port = getConfig('radius_port', 1812); |
|
41 $secret = getConfig('radius_secret', ''); |
|
42 $method = getConfig('radius_method', 'pap'); |
|
43 if ( empty($server) ) |
|
44 // bad server? break out and continue the Enano auth chain |
|
45 return null; |
|
46 |
|
47 // We're ready to do a RADIUS auth attempt |
|
48 try |
|
49 { |
|
50 $radius = new RadiusAuth($server, $secret, $port); |
|
51 $auth_result = $radius->authenticate($userinfo['username'], $userinfo['password'], $method); |
|
52 } |
|
53 catch ( RadiusError $e ) |
|
54 { |
|
55 return array( |
|
56 'mode' => 'error', |
|
57 'error' => "The RADIUS interface returned a technical error." |
|
58 ); |
|
59 } |
|
60 |
|
61 if ( $auth_result ) |
|
62 { |
|
63 // RADIUS authentication was successful. |
|
64 $username = $db->escape(strtolower($userinfo['username'])); |
|
65 $q = $db->sql_query("SELECT user_id, password FROM " . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '$username';"); |
|
66 if ( !$q ) |
|
67 $db->_die(); |
|
68 if ( $db->numrows() < 1 ) |
|
69 { |
|
70 // This user doesn't exist. |
|
71 // Is creating it our job? |
|
72 if ( getConfig('radius_disable_local_auth', 0) == 1 ) |
|
73 { |
|
74 // Yep, register him |
|
75 $email = strtolower($userinfo['username']) . '@' . getConfig('radius_email_domain', 'localhost'); |
|
76 $random_pass = md5(microtime() . mt_rand()); |
|
77 // load the language |
|
78 $session->register_guest_session(); |
|
79 $reg_result = $session->create_user($userinfo['username'], $random_pass, $email); |
|
80 if ( $reg_result != 'success' ) |
|
81 { |
|
82 // o_O |
|
83 // Registration failed. |
|
84 return array( |
|
85 'mode' => 'error', |
|
86 'error' => 'Your username and password were valid, but there was a problem instanciating your local user account.' |
|
87 ); |
|
88 } |
|
89 // Get user ID |
|
90 $q = $db->sql_query("SELECT user_id, password FROM " . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '$username';"); |
|
91 if ( !$q ) |
|
92 $db->_die(); |
|
93 if ( $db->numrows() < 1 ) |
|
94 return array( |
|
95 'mode' => 'error', |
|
96 'error' => 'Your username and password were valid, but there was a problem getting your user ID.' |
|
97 ); |
|
98 $row = $db->fetchrow(); |
|
99 $db->free_result(); |
|
100 // Quick - lock the account |
|
101 $q = $db->sql_query('UPDATE ' . table_prefix . "users SET password = 'Locked by RADIUS plugin', password_salt = 'Locked by RADIUS plugin' WHERE user_id = {$row['user_id']};"); |
|
102 if ( !$q ) |
|
103 $db->_die(); |
|
104 |
|
105 $row['password'] = 'Locked by RADIUS plugin'; |
|
106 } |
|
107 else |
|
108 { |
|
109 // Nope. Just let Enano fail it properly. |
|
110 return null; |
|
111 } |
|
112 } |
|
113 else |
|
114 { |
|
115 $row = $db->fetchrow(); |
|
116 $db->free_result(); |
|
117 } |
|
118 |
|
119 $session->register_session($row['user_id'], $userinfo['username'], $row['password'], $level, $remember); |
|
120 return true; |
|
121 } |
|
122 else |
|
123 { |
|
124 // RADIUS authentication failed. |
|
125 |
|
126 // Are local logons allowed? |
|
127 if ( getConfig('radius_disable_local_auth', 0) == 0 ) |
|
128 { |
|
129 // Yes, allow auth to continue |
|
130 return null; |
|
131 } |
|
132 |
|
133 // Block the login attempt unless the username is a local admin. |
|
134 $username = $db->escape(strtolower($userinfo['username'])); |
|
135 $q = $db->sql_query("SELECT user_level FROM " . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '$username';"); |
|
136 if ( !$q ) |
|
137 $db->_die(); |
|
138 if ( $db->numrows() > 0 ) |
|
139 { |
|
140 // Well, the user exists... |
|
141 list($ul) = $db->fetchrow_num(); |
|
142 $db->free_result(); |
|
143 if ( $ul >= USER_LEVEL_ADMIN ) |
|
144 { |
|
145 // They're an admin, allow local logon |
|
146 return null; |
|
147 } |
|
148 } |
|
149 $db->free_result(); |
|
150 |
|
151 // User doesn't exist, or is not an admin, and users are not allowed to log on locally. Lock them out. |
|
152 $q = $db->sql_query('INSERT INTO ' . table_prefix . "lockout(ipaddr, timestamp, action, username)\n" |
|
153 . " VALUES('" . $db->escape($_SERVER['REMOTE_ADDR']) . "', " . time() . ", 'credential', '" . $db->escape($userinfo['username']) . "');"); |
|
154 if ( !$q ) |
|
155 $db->_die(); |
|
156 |
|
157 return array( |
|
158 'mode' => 'error', |
|
159 'error' => 'Invalid RADIUS authentication credentials.' |
|
160 ); |
|
161 } |
|
162 } |
|
163 |
|
164 // Registration blocking hook |
|
165 if ( getConfig('radius_disable_local_auth', 0) == 1 ) |
|
166 { |
|
167 $plugins->attachHook('ucp_register_validate', 'radius_auth_reg_block($error);'); |
|
168 } |
|
169 |
|
170 function radius_auth_reg_block(&$error) |
|
171 { |
|
172 $error = 'Registration on this website is disabled because RADIUS authentication is configured. Please log in using a valid RADIUS username and password, and an account will be created for you automatically.'; |
|
173 } |
|
174 |
|
175 // |
|
176 // ADMIN |
|
177 // |
|
178 |
|
179 $plugins->attachHook('session_started', 'radius_session_hook();'); |
|
180 |
|
181 if ( getConfig('radius_disable_local_auth', 0) == 1 ) |
|
182 { |
|
183 $plugins->attachHook('common_post', 'radius_tou_hook();'); |
|
184 } |
|
185 |
|
186 function radius_session_hook() |
|
187 { |
|
188 global $db, $session, $paths, $template, $plugins; // Common objects |
|
189 |
|
190 // Register the admin page |
|
191 $paths->addAdminNode('adm_cat_security', 'RADIUS Authentication', 'RadiusConfig'); |
|
192 |
|
193 // Disable password change |
|
194 if ( getConfig('radius_disable_local_auth', 0) == 1 && $session->user_level < USER_LEVEL_ADMIN ) |
|
195 { |
|
196 $link_text = getConfig('radius_password_text', false); |
|
197 if ( empty($link_text) ) |
|
198 $link_text = false; |
|
199 $link_url = str_replace('%u', $session->username, getConfig('radius_password_url', '')); |
|
200 if ( empty($link_url) ) |
|
201 $link_url = false; |
|
202 $session->disable_password_change($link_url, $link_text); |
|
203 } |
|
204 } |
|
205 |
|
206 function radius_tou_hook() |
|
207 { |
|
208 global $db, $session, $paths, $template, $plugins; // Common objects |
|
209 |
|
210 // Are we pending TOU acceptance? |
|
211 if ( $session->user_logged_in && !$session->on_critical_page() && trim(getConfig('register_tou', '')) != '' ) |
|
212 { |
|
213 $q = $db->sql_query('SELECT account_active FROM ' . table_prefix . "users WHERE user_id = $session->user_id;"); |
|
214 if ( !$q ) |
|
215 $db->_die(); |
|
216 |
|
217 list($active) = $db->fetchrow_num(); |
|
218 $db->free_result(); |
|
219 if ( $active == 1 ) |
|
220 { |
|
221 // Pending TOU accept |
|
222 // Basically, what we do here is force the user to accept the TOU and record it by setting account_active to 2 instead of a 1 |
|
223 // A bit of a hack, but hey, it works, at least in 1.1.8. |
|
224 // In 1.1.7, it just breaks your whole account, and $session->on_critical_page() is broken in 1.1.7 so you won't even be able |
|
225 // to go the admin CP and re-activate yourself. Good times... erhm, sorry. |
|
226 |
|
227 if ( isset($_POST['tou_agreed']) && $_POST['tou_agreed'] === 'I accept the terms and conditions displayed on this site' ) |
|
228 { |
|
229 // Accepted |
|
230 $q = $db->sql_query('UPDATE ' . table_prefix . "users SET account_active = 2 WHERE user_id = $session->user_id;"); |
|
231 if ( !$q ) |
|
232 $db->_die(); |
|
233 |
|
234 return true; |
|
235 } |
|
236 |
|
237 global $output, $lang; |
|
238 $output->set_title('Terms of Use'); |
|
239 $output->header(); |
|
240 |
|
241 ?> |
|
242 <p>Please read and accept the following terms:</p> |
|
243 |
|
244 <div style="border: 1px solid #000000; height: 300px; width: 60%; clip: rect(0px,auto,auto,0px); overflow: auto; background-color: #FFF; margin: 0 auto; padding: 4px;"> |
|
245 <?php |
|
246 $terms = getConfig('register_tou', ''); |
|
247 echo RenderMan::render($terms); |
|
248 ?> |
|
249 </div> |
|
250 |
|
251 <form method="post"> |
|
252 <p style="text-align: center;"> |
|
253 <label> |
|
254 <input tabindex="7" type="checkbox" name="tou_agreed" value="I accept the terms and conditions displayed on this site" /> |
|
255 <b><?php echo $lang->get('user_reg_lbl_field_tou'); ?></b> |
|
256 </label> |
|
257 </p> |
|
258 <p style="text-align: center;"> |
|
259 <input type="submit" value="Continue" /> |
|
260 </p> |
|
261 </form> |
|
262 |
|
263 <?php |
|
264 |
|
265 $output->footer(); |
|
266 |
|
267 $db->close(); |
|
268 exit; |
|
269 } |
|
270 } |
|
271 } |
|
272 |
|
273 function page_Admin_RadiusConfig() |
|
274 { |
|
275 // Security check |
|
276 global $db, $session, $paths, $template, $plugins; // Common objects |
|
277 if ( $session->auth_level < USER_LEVEL_ADMIN ) |
|
278 return false; |
|
279 |
|
280 if ( isset($_POST['submit']) ) |
|
281 { |
|
282 setConfig('radius_enable', isset($_POST['radius_enable']) ? '1' : '0'); |
|
283 setConfig('radius_server', $_POST['radius_server']); |
|
284 setConfig('radius_port', intval($_POST['radius_port']) > 0 && intval($_POST['radius_port']) < 65535 ? intval($_POST['radius_port']) : 1812 ); |
|
285 setConfig('radius_secret', $_POST['radius_secret']); |
|
286 setConfig('radius_disable_local_auth', isset($_POST['radius_disable_local_auth']) ? '1' : '0'); |
|
287 setConfig('radius_password_text', $_POST['radius_password_text']); |
|
288 setConfig('radius_password_url', $_POST['radius_password_url']); |
|
289 setConfig('radius_email_domain', $_POST['radius_email_domain']); |
|
290 setConfig('radius_method', $_POST['radius_method']); |
|
291 |
|
292 echo '<div class="info-box">Your changes have been saved.</div>'; |
|
293 } |
|
294 |
|
295 acp_start_form(); |
|
296 ?> |
|
297 <div class="tblholder"> |
|
298 <table border="0" cellspacing="1" cellpadding="4"> |
|
299 <tr> |
|
300 <th colspan="2"> |
|
301 RADIUS Authentication Configuration |
|
302 </th> |
|
303 </tr> |
|
304 |
|
305 <!-- RADIUS enable --> |
|
306 |
|
307 <tr> |
|
308 <td class="row2" style="width: 50%;"> |
|
309 Enable RADIUS authentication: |
|
310 </td> |
|
311 <td class="row1" style="width: 50%;"> |
|
312 <label> |
|
313 <input type="checkbox" name="radius_enable" <?php if ( getConfig('radius_enable', 0) ) echo 'checked="checked" '; ?>/> |
|
314 Enabled |
|
315 </label> |
|
316 </td> |
|
317 </tr> |
|
318 |
|
319 <!-- Server --> |
|
320 |
|
321 <tr> |
|
322 <td class="row2"> |
|
323 RADIUS server: |
|
324 </td> |
|
325 <td class="row1"> |
|
326 <input type="text" name="radius_server" value="<?php echo htmlspecialchars(getConfig('radius_server', '')); ?>" size="15" /> |
|
327 Port: |
|
328 <input type="text" name="radius_port" value="<?php echo getConfig('radius_port', 1812); ?>" size="5" /> |
|
329 </td> |
|
330 </tr> |
|
331 |
|
332 <!-- Secret --> |
|
333 |
|
334 <tr> |
|
335 <td class="row2"> |
|
336 Shared secret: |
|
337 </td> |
|
338 <td class="row1"> |
|
339 <input type="text" name="radius_secret" value="<?php echo htmlspecialchars(getConfig('radius_secret', '')); ?>" size="30" /> |
|
340 </td> |
|
341 </tr> |
|
342 |
|
343 <!-- Auth method --> |
|
344 |
|
345 <tr> |
|
346 <td class="row2"> |
|
347 Authentication method: |
|
348 </td> |
|
349 <td class="row1"> |
|
350 <select name="radius_method"> |
|
351 <?php |
|
352 $methods = array( |
|
353 'pap' => 'PAP', |
|
354 'chap' => 'CHAP', |
|
355 'mschap' => 'MS-CHAP v1', |
|
356 'mschapv2' => 'MS-CHAP v2' |
|
357 ); |
|
358 foreach ( $methods as $method => $name ) |
|
359 { |
|
360 $select = getConfig('radius_method', 'pap') == $method ? ' selected="selected"' : ''; |
|
361 echo "<option value=\"$method\"{$select}>$name</option>"; |
|
362 } |
|
363 ?> |
|
364 </select> |
|
365 </td> |
|
366 </tr> |
|
367 |
|
368 <!-- Block local auth --> |
|
369 |
|
370 <tr> |
|
371 <td class="row2"> |
|
372 Enforce RADIUS for single-sign-on:<br /> |
|
373 <small>Use this option to force RADIUS passwords and accounts to be used, regardless of local accounts, except for administrators.</small> |
|
374 </td> |
|
375 <td class="row1"> |
|
376 <label> |
|
377 <input type="checkbox" name="radius_disable_local_auth" <?php if ( getConfig('radius_disable_local_auth', 0) ) echo 'checked="checked" '; ?>/> |
|
378 Enabled |
|
379 </label> |
|
380 </td> |
|
381 </tr> |
|
382 |
|
383 <!-- E-mail domain --> |
|
384 |
|
385 <tr> |
|
386 <td class="row2"> |
|
387 E-mail address domain for autoregistered users:<br /> |
|
388 <small>When a user is automatically registered, this domain will be used as the domain for their e-mail address. This way, activation e-mails will |
|
389 (ideally) reach the user.</small> |
|
390 </td> |
|
391 <td class="row1"> |
|
392 <input type="text" name="radius_email_domain" value="<?php echo htmlspecialchars(getConfig('radius_email_domain', '')); ?>" size="30" /> |
|
393 </td> |
|
394 </tr> |
|
395 |
|
396 <!-- Site password change link --> |
|
397 |
|
398 <tr> |
|
399 <td class="row2"> |
|
400 External password management link:<br /> |
|
401 <small>Enter a URL here to link to from Enano's Change Password page. Leave blank to not display a link. The text "%u" will be replaced with the user's username.</small> |
|
402 </td> |
|
403 <td class="row1"> |
|
404 Link text: <input type="text" name="radius_password_text" value="<?php echo htmlspecialchars(getConfig('radius_password_text', '')); ?>" size="30" /><br /> |
|
405 Link URL: <input type="text" name="radius_password_url" value="<?php echo htmlspecialchars(getConfig('radius_password_url', '')); ?>" size="30" /> |
|
406 </td> |
|
407 </tr> |
|
408 |
|
409 <tr> |
|
410 <th class="subhead" colspan="2"> |
|
411 <input type="submit" name="submit" value="Save changes" /> |
|
412 </th> |
|
413 </tr> |
|
414 </table> |
|
415 </div> |
|
416 <?php |
|
417 echo '</form>'; |
|
418 } |