4 * Copyright (c) 2003, The Burgiss Group, LLC
5 * This source code is part of eWiki LiveUser Plugin.
7 * eWiki LiveUser Plugin is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version.
12 * eWiki LiveUser Plugin is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with Wiki LiveUser Plugin; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * ewiki: liveuser authentication plugin
25 * @author andy fundinger <afundinger@burgiss.com>
26 * @author alex wan <alex@burgiss.com>
27 * @author jeremy mikola <jmikola@arsjerm.net>
29 * Without auth_perm_ring or perm_liveuser this plugin will merely require
30 * authentication to edit (wherein ring is set to two) and hide all control
31 * links from users who are not logged in if EWIKI_PROTECTED_MODE_HIDING is set.
33 * A login form will be displayed if you try to edit without logging in.
35 * This plugin opens it's own database and must be included BEFORE the ewiki
38 * @contributer jeffrey engleman
39 -added ewiki_check_passwd
42 require_once(dirname(__FILE__).'/liveuser_aux.php');
44 define('EWIKI_LOGGEDIN_RING', 1);
45 define('EWIKI_MIN_DICT_WORD_LENGTH', 3);
46 //Path to dictionary file, passdict is included for this
47 //define('EWIKI_PASS_DICT_PATH', '/path/to/dictionary');
48 define('EWIKI_PASSWORD_COMPLEXITY', 56);
49 //Maximum delay before shutting down the system (in seconds)
50 define('EWIKI_LIVEUSER_LOGIN_SHUTDOWN_DELAY', 30);
52 // Set passwords to expire in 180 days, actually expiring passwords requires:
53 // * a uservars plugin
54 // * /tools/scripts/passwd_expiration to run daily (not yet released)
55 // * the /plugins/passwd_expire plugin (not yet released)
56 define('EWIKI_PASSWD_LIFETIME',180);
58 log logins for guess rate limiter, table structure should be:
60 CREATE TABLE `liveweb_login_log` (
61 `auth_user_handle` varchar(32) NOT NULL default '',
62 `php_session_id` varchar(40) NOT NULL default '',
63 `ssl_session_id` varchar(16) default '',
64 `ip_address` varchar(16) NOT NULL default '',
65 `time` timestamp(14) NOT NULL,
66 `delay` tinyint(1) NOT NULL default '0',
67 `success` tinyint(1) NOT NULL default '0',
68 PRIMARY KEY (`time`,`auth_user_handle`)
71 in the LiveUser database
73 if you allow only SSL connections set ssl_session_id to "NOT NULL"
75 When enabled a failure to successfully log will result in a die() call.
78 @define('EWIKI_AUTH_DEFAULT_RING', 3);
79 define('EWIKI_NOT_LOGGEDIN_RING', EWIKI_AUTH_DEFAULT_RING);
81 // ewiki callbacks for auth query, login, logout, and changePassword methods
82 $ewiki_plugins['auth_query'][0] = 'ewiki_auth_query_liveuser';
83 $ewiki_plugins['page']['LogIn'] = 'ewiki_page_liveuser_login';
84 $ewiki_plugins['page']['LogOut'] = 'ewiki_page_liveuser_logout';
85 $ewiki_plugins['page']['ChangePassword'] = 'ewiki_page_liveuser_chpw';
88 Policies for the guess rate limiter, name entry descriptively for alerts:
90 'where' => Where clause for interesting records "?" is current username
91 'count' => Empty entry, will store count of respondant records when queried
92 'threshold' => Minimum records before adding delay
93 'coefficient' => delay to add per record
94 'alert_mod' => alerts will be sent when count%alert_mod==0
98 //Slow down if 50 bad passwords have been entered this hour.
99 $liveuser_delay_policies['Hourly_bad_PWs']=array(
100 'where' => 'time> DATE_SUB(NOW(), INTERVAL 1 HOUR)',
103 'coefficient' => 5/120,
106 //Slow down if 200 bad passwords have been entered today.
107 $liveuser_delay_policies['Daily_bad_PWs']=array(
108 'where' => 'time> DATE_SUB(NOW(), INTERVAL 1 DAY)',
111 'coefficient' => .0357,
114 //Send alert if one account gets 10 bad passwords in a day
115 $liveuser_delay_policies['Acct_bad_PWs']=array(
116 'where' => 'time> DATE_SUB(NOW(), INTERVAL 1 DAY) AND auth_user_handle=?',
123 define('ALERT_RECIPIENTS', 'webmaster@127.0.0.1');
124 define('ALERT_SUBJECT',"LU IDS ALERT");
126 /* ignore username and password form data if cancelling a login process */
127 /* otherwise calculate and ennact a login delay.*/
128 if (isset($_REQUEST['username'])&&!isset($_REQUEST['cancel_login'])){
129 $username=$_REQUEST['username'];
130 $authlog="LOGIN ATTEMPT USERNAME:".$username." cancel_login==".$_REQUEST['cancel_login'];
132 liveweb_query_delay_data($liveuser_delay_policies);
133 //var_dump($liveuser_delay_policies);
135 $totalDelay=liveweb_get_total_delay($liveuser_delay_policies);
137 //echo(" total delay $totalDelay");
139 if($totalDelay>EWIKI_LIVEUSER_LOGIN_SHUTDOWN_DELAY){
140 $liveuserConfig['login']['username'] = '';
141 $liveuserConfig['login']['password'] = '';
143 //using usleep rather than sleep() because sleep() is integer seconds only.
144 usleep(1000000*$totalDelay);
148 $liveuserConfig['login']['username'] = '';
149 $liveuserConfig['login']['password'] = '';
153 /* Setting $liveuserConfig['login']['username'] and $liveuserConfig['login']['password']
154 * to '' causes the login to be ignored by the LiveUser system.
155 * In Liveuser.php during the tryLogin function on line 665,
156 * it sees the handle is empty. It then tries to login based on a cookie,
157 * but in line 171 that _options['cookie'] is not set so it goes to line 693
158 * sees that _options['login']['username'] and _options['login']['password']
159 * are empty, tries to run _options['login']['function'] which is also set to ''
160 * so it fails out of the if and hits line 715 where it returns false negating the login.
163 // instantiate a LiveUser object from the config array
164 $liveuser =& LiveUser::factory($liveuserConfig);
166 if (isset($_REQUEST['username'])&&!isset($_REQUEST['cancel_login'])){
167 if($totalDelay>EWIKI_LIVEUSER_LOGIN_SHUTDOWN_DELAY){
170 //Get data as we would for logging
171 $loginData=ewiki_liveuser_get_login_data();
173 //Tests login, updates $username
174 if ($username=$liveuser->getHandle()){
175 //Clear delay flags with matching handle, php session, ssl session, and ip
178 UPDATE `liveweb_login_log` set delay=0
179 WHERE time> DATE_SUB(NOW(), INTERVAL 1 DAY)
180 AND auth_user_handle=? AND php_session_id=?
181 AND ssl_session_id=? AND ip_address=?',
182 array($username,$loginData['php_session_id'],
183 $loginData['ssl_session_id'],$loginData['ip_address']));
184 }else{//Failed login attempt with logins enabled
185 $authlog.="LOGIN FAILED USERNAME:".$_REQUEST['username']." PASSWORD:".$_REQUEST['password'];
186 ewiki_liveuser_IDS_alerts($liveuser_delay_policies,$loginData['auth_user_handle']);
191 //Tests login, updates $username NB: $username may or may not have been set above.
192 if($username=$liveuser->getHandle()) {
193 //Reset username based on the autheticated username, should be no change.
194 $GLOBALS['ewiki_author'] = $username;
195 $GLOBALS['ewiki_auth_user'] = $username;
197 $GLOBALS['ewiki_author'] = null;
198 $GLOBALS['ewiki_auth_user'] = null;
199 define('EWIKI_AUTO_EDIT',0);
202 // html page output response messages
203 $ewiki_t['en']['LOGINSHUTDOWN'] = "<p>We're sorry, but logins are temporarily disabled, please try again later.</p>";
204 $ewiki_t['en']['CANNOTCHANGEPAGE'] = '<p>This page cannot be changed. Perhaps you can <a href="'.EWIKI_SCRIPT.'LogIn">LogIn</a> and change it then.</p>';
205 $ewiki_t['en']['NOTLOGGEDIN'] = '<p>You are not logged in. You must <a href="'.EWIKI_SCRIPT.'LogIn">LogIn</a> to access some features of this site.</p>';
206 $ewiki_t['en']['LOGGEDINALREADY'] = '<p>You are already logged in.</p>';
207 $ewiki_t['en']['LOGGEDIN'] = '<p>You have logged in as '.$GLOBALS['ewiki_author'].'.';
208 $ewiki_t['en']['LOGGEDOUT'] = '<p>You have been logged out.</p>';
209 $ewiki_t['en']['LOGINFAILED'] = '<p>You supplied an invalid username or password while logging in.</p>';
210 $ewiki_t['en']['LOGINFORM'] = '
211 <p>Please identify yourself with a username and a password:</p>
213 <form action="" method="post">
214 <table border="0" bgcolor="#eeeeee" cellspacing="0" cellpadding="4">
215 <tr valign="top" align="left">
217 <td><input type="text" name="username" size="32" maxlength="32"></td>
219 <tr valign="top" align="left">
221 <td><input type="password" name="password" size="32" maxlength="32"></td>
226 <input type="submit" name="submit_login" value="Login now">
227 <input type="submit" name="cancel_login" value="Cancel">
234 $ewiki_t['en']['CHPW_BADNEW_COMPLEXITY']= '<p>Your password has been deemed insecure because it did not meet the complexity requirements.</p>
235 <p>Suggested Remedies:</p><ul><li>Increase the length of your new password.</li><li>Combine upper and lower case letters</li>
236 <li>Use special characters and numbers.</li></ul>';
237 $ewiki_t['en']['CHPW_BADNEW_USERNAME']='<p>Your password has been deemed insecure because it matches your username too closley.</p><p>Suggested Remedy:</p><ul><li>Do not use your username
238 in your password in any way.</li></ul>';
239 $ewiki_t['en']['CHPW_BADNEW_DICTIONARY']='<p>Your password has been deemed insecure because part of it can be found on our list of common passwords.</p><p>Suggested Remedy:</p>
240 <ul><li>Remove all dictionary words from your password.</li><li>Remove common sequences from your password.</li></ul>';
241 $ewiki_t['en']['CHPW_BADOLD'] = '<p>You have misentered your old password.</p>';
242 $ewiki_t['en']['CHPW_SUCCESS'] = '<p>Your password has been changed.</p>';
243 $ewiki_t['en']['CHPW_NOMATCH'] = '<p>Your new passwords did not match. Please re-enter them.</p>';
244 $ewiki_t['en']['CHPW_SAMEOLD'] = '<p>Your new password is the same as your old password.</p>';
245 $ewiki_t['en']['CHPW_ERROR'] = '<p>An error occurred while attempting to change your password.</p>';
246 $ewiki_t['en']['CHPW_FORM'] = '
247 <p>Please enter your old password once and your new password twice in the blanks below:</p>
249 <form action="?id=ChangePassword" method="post">
250 <table border="0" bgcolor="#eeeeee" align="center" cellspacing="0" cellpadding="4">
251 <tr valign="top" align="left">
252 <td>Old Password:</td>
253 <td><input type="password" name="oldpassword" size="32" maxlength="32"></td>
255 <tr valign="top" align="left">
256 <td>New Password:</td>
257 <td><input type="password" name="newpassword1" size="32" maxlength="32"></td>
259 <tr valign="top" align="left">
260 <td>Repeat New Password:</td>
261 <td><input type="password" name="newpassword2" size="32" maxlength="32"></td>
266 <input type="submit" name="submit" value="Change Password">
267 <input type="button" name="cancel_pwchng" value="Cancel">
272 $ewiki_t['en']['PASS_DICTIONARY_READ_ERROR'] = "<p>An error was encountered while validating your password.</p>";
274 * processes login for the current user.
278 * @return string page output response to login attempt
280 function ewiki_page_liveuser_login($id, $data)
282 global $liveuser,$liveuser_delay_policies;
284 liveweb_query_delay_data($liveuser_delay_policies);
285 //var_dump($liveuser_delay_policies);
287 $totalDelay=liveweb_get_total_delay($liveuser_delay_policies);
289 $o=ewiki_make_title($id, $id, 2);
290 if($totalDelay > EWIKI_LIVEUSER_LOGIN_SHUTDOWN_DELAY){
291 $o.=ewiki_t('LOGINSHUTDOWN');
294 if (isset($_REQUEST['submit_login']) or isset($_REQUEST['submit_login_img_x'])) {
295 // login form submission, print form response
296 return $o.($liveuser->isLoggedIn() ? ewiki_t('LOGGEDIN') : ewiki_t('LOGINFAILED').ewiki_t('LOGINFORM'));
298 // all other calls, print login status or form output
299 return $o.($liveuser->isLoggedIn() ? ewiki_t('LOGGEDINALREADY') : ewiki_t('LOGINFORM'));
305 * logs out the current user.
309 * @return string page output for logout message
311 function ewiki_page_liveuser_logout($id, $data)
316 return ewiki_make_title($id, $id, 2).ewiki_t('LOGGEDOUT');
320 * changes current user's password based on form input
326 function ewiki_page_liveuser_chpw($id, $data)
328 global $liveuser, $liveuserAuthAdmin;
330 // if form was not submitted, return page output for form
331 if (!isset($_REQUEST['oldpassword'])) {
332 return ewiki_make_title($id, $id, 2).ewiki_t('CHPW_FORM');
335 // ensure that original password is valid, and that new passwords match
336 if ($liveuser->getProperty('passwd') != $liveuserAuthAdmin->encryptPW($_REQUEST['oldpassword'])) {
337 return ewiki_make_title($id, $id, 2).ewiki_t('CHPW_BADOLD').ewiki_t('CHPW_FORM');
338 } else if ($_REQUEST['newpassword1'] != $_REQUEST['newpassword2']) {
339 return ewiki_make_title($id, $id, 2).ewiki_t('CHPW_NOMATCH').ewiki_t('CHPW_FORM');
340 } else if ($_REQUEST['newpassword1'] == $_REQUEST['oldpassword']){
341 return ewiki_make_title($id, $id, 2).ewiki_t('CHPW_SAMEOLD').ewiki_t('CHPW_FORM');
343 //$time=getmicrotime();
344 $password_status=ewiki_check_passwd($_REQUEST['newpassword1'],$liveuser->getHandle());
345 //$end=getmicrotime();
347 if ($password_status!='good passwd') {
348 if($password_status=='read error'){
349 return ewiki_make_title($id, $id, 2).ewiki_t('PASS_DICTIONARY_READ_ERROR');
351 return ewiki_make_title($id, $id, 2).ewiki_t($password_status).'<!--'.$password_status.'-->'.ewiki_t('CHPW_FORM');
356 if ($liveuserAuthAdmin->updateUser($liveuser->getProperty('authUserId'), $liveuser->getHandle(), $_REQUEST['newpassword2']) === true) {
357 ewiki_set_uservar("passwdstatus", 'good', $GLOBALS['ewiki_auth_user']);
358 ewiki_set_uservar("passwdexpiredate", time()+(60*60*24*EWIKI_PASSWD_LIFETIME),$GLOBALS['ewiki_auth_user']);
359 return ewiki_make_title($id, $id, 2).ewiki_t('CHPW_SUCCESS');
361 return ewiki_make_title($id, $id, 2).ewiki_t('CHPW_ERROR');
367 * checks if the current user is logged in. this function will alter the output
368 * parameter if the cancel login form event is being processed.
370 * @param string data page data (not used)
371 * @param string ewiki_author
372 * @param int ewiki_ring
373 * @param int force_query
374 * @global string ewiki_errmsg returns error message for user on failure
375 * @return boolean true if the current user is logged in, false otherwise
377 function ewiki_auth_query_liveuser(&$data, $force_query = 0)
379 global $liveuser, $ewiki_config, $ewiki_errmsg, $ewiki_ring;
381 if ($liveuser->isLoggedIn()) {
382 $ewiki_ring = EWIKI_LOGGEDIN_RING;
386 if (isset($_REQUEST['cancel_login'])) {
387 $ewiki_errmsg = ewiki_t('FORBIDDEN');
388 } else if (isset($_REQUEST['submit_login'])) {
389 $ewiki_errmsg = ewiki_t('LOGINFAILED').ewiki_t('LOGINFORM');
391 $ewiki_errmsg = ewiki_t('LOGINFORM');
396 $ewiki_ring = EWIKI_NOT_LOGGEDIN_RING;
402 //Gets general data about login for loging etc.
403 function ewiki_liveuser_get_login_data(){
406 $success=$liveuser->isLoggedIn();
409 'auth_user_handle' => $_REQUEST['username'],
410 'php_session_id' => session_id(),
411 'ssl_session_id' => ($uu = $_ENV['SSL_SESSION_ID']?$uu:'SSL not enabled'),
412 'ip_address' => $_SERVER['REMOTE_ADDR'],
413 'delay' => !$success,
414 'success' => $success);
416 return($requestInfo);
420 function liveuser_loglogin(){
423 if (!isset($_REQUEST['username'])){
427 $requestInfo= ewiki_liveuser_get_login_data();
429 //store ip and sslid in session variables so we can check them later.
430 $_SESSION['loginInfo']['ip_address']=$requestInfo['ip_address'];
431 $_SESSION['loginInfo']['ssl_session_id']=$requestInfo['ssl_session_id'];
433 //var_dump($requestInfo);
435 // You must create a new array to pass to this function, passing
436 // $requestInfo does not work
437 if($liveuserDB->query('INSERT INTO '.LW_PREFIX.'_login_log (auth_user_handle, '.
438 'php_session_id, ssl_session_id,ip_address,delay,success,time) '.
439 'VALUES (?, ?, ?, ?, ?, ?, NOW())',
440 array($requestInfo['auth_user_handle'],$requestInfo['php_session_id'],
441 $requestInfo['ssl_session_id'],$requestInfo['ip_address'],
442 $requestInfo['delay'],$requestInfo['success'] ))!=DB_OK)
444 die('Failure in database connection, unable to continue');
449 // Calculates delay, utility function
450 function liveweb_delay_calc($count, $threshold, $coeff){
451 return(max($count-$threshold,0)*$coeff);
453 function liveweb_get_total_delay($liveuser_delay_policies){
456 foreach($liveuser_delay_policies as $currPolicy){
457 $delay += liveweb_delay_calc($currPolicy['count'], $currPolicy['threshold'], $currPolicy['coefficient']);
463 //Populates 'count' with the number of respondant records
464 function liveweb_query_delay_data(&$liveuser_delay_policies){
467 foreach ($liveuser_delay_policies as $polNum => $currPolicy){
468 $liveuser_delay_policies[$polNum]['count']=0+$liveuserDB->getOne('SELECT sum(delay) as delay_count from '.
469 '`'.LW_PREFIX.'_login_log` WHERE '.$currPolicy['where'],array($_REQUEST['username']));
473 //Sends alerts if need be
474 function ewiki_liveuser_IDS_alerts($liveuser_delay_policies,$username){
476 foreach ($liveuser_delay_policies as $polName => $currPolicy){
477 if((++$liveuser_delay_policies[$polName]['count'])%$currPolicy['alert_mod']==0){
487 foreach ($liveuser_delay_policies as $polName => $currPolicy){
488 $m_text.="$polName: {$currPolicy['count']}(mod {$currPolicy['alert_mod']})\n";
491 ($server = $_SERVER["HTTP_HOST"]) or
492 ($server = $_SERVER["SERVER_NAME"]);
494 $m_text.="$username attempting to login\n";
496 $m_text.= "\n(".EWIKI_PAGE_INDEX." on http://$server/)\n";
497 $m_text.= $_SERVER["SERVER_ADMIN"]."\n";
499 //$m_from = EWIKI_NOTIFY_SENDER."@$server";
500 $m_from = "Alerts@$server";
501 $m_subject = ALERT_SUBJECT;
502 $m_to = ALERT_RECIPIENTS;
504 //echo("mail($m_to, $m_subject, $m_text, 'From: \'$s_2\' <$m_from>\nX-Mailer: ErfurtWiki/'.EWIKI_VERSION);");
505 mail($m_to, $m_subject, $m_text, "From: \"$s_2\" <$m_from>\nX-Mailer: ErfurtWiki/".EWIKI_VERSION);