changed git call from https to git readonly
[atutor.git] / mods / wiki / plugins / auth-liveuser / auth_liveuser.php
1 <?php
2
3 /**
4  * Copyright (c) 2003, The Burgiss Group, LLC
5  * This source code is part of eWiki LiveUser Plugin.
6  *
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.
11  *
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
15  * for more details.
16  *
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
20  */
21
22 /**
23  * ewiki: liveuser authentication plugin
24  *
25  * @author andy fundinger <afundinger@burgiss.com>
26  * @author alex wan <alex@burgiss.com>
27  * @author jeremy mikola <jmikola@arsjerm.net>
28  *
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.
32  * 
33  * A login form will be displayed if you try to edit without logging in.
34  *
35  * This plugin opens it's own database and must be included BEFORE the ewiki 
36  * database is opened.
37  *
38  * @contributer jeffrey engleman
39      -added ewiki_check_passwd
40  */
41  
42 require_once(dirname(__FILE__).'/liveuser_aux.php');
43
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);
51
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);
57 /*
58 log logins for guess rate limiter, table structure should be:
59
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`)
69 ) TYPE=MyISAM; 
70
71 in the LiveUser database
72
73 if you allow only SSL connections set ssl_session_id to "NOT NULL"
74
75 When enabled a failure to successfully log will result in a die() call.
76 */
77
78 @define('EWIKI_AUTH_DEFAULT_RING', 3);
79 define('EWIKI_NOT_LOGGEDIN_RING', EWIKI_AUTH_DEFAULT_RING);
80
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';
86
87 /* 
88 Policies for the guess rate limiter, name entry descriptively for alerts:
89 array(
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
95 )
96 */
97
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)',
101     'count' => 0,
102     'threshold' => 50,
103     'coefficient' => 5/120,
104     'alert_mod' => 50
105 );
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)',
109     'count' => 0,
110     'threshold' => 200,
111     'coefficient' => .0357,
112     'alert_mod' => 200
113 );
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=?',
117     'count' => 0,
118     'threshold' => 0,
119     'coefficient' => 0,
120     'alert_mod' => 10
121 );
122         //Who to notify
123     define('ALERT_RECIPIENTS', 'webmaster@127.0.0.1');
124     define('ALERT_SUBJECT',"LU IDS ALERT");
125
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'];
131     
132     liveweb_query_delay_data($liveuser_delay_policies);
133     //var_dump($liveuser_delay_policies);
134     
135     $totalDelay=liveweb_get_total_delay($liveuser_delay_policies);
136     
137     //echo(" total delay $totalDelay");
138
139     if($totalDelay>EWIKI_LIVEUSER_LOGIN_SHUTDOWN_DELAY){
140         $liveuserConfig['login']['username'] = ''; 
141         $liveuserConfig['login']['password'] = '';        
142     }else{
143         //using usleep rather than sleep() because sleep() is integer seconds only.
144         usleep(1000000*$totalDelay);    
145     }
146         
147 }else{    
148     $liveuserConfig['login']['username'] = ''; 
149     $liveuserConfig['login']['password'] = '';
150 }
151
152
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.
161 */
162
163 // instantiate a LiveUser object from the config array
164 $liveuser =& LiveUser::factory($liveuserConfig);
165
166 if (isset($_REQUEST['username'])&&!isset($_REQUEST['cancel_login'])){ 
167     if($totalDelay>EWIKI_LIVEUSER_LOGIN_SHUTDOWN_DELAY){
168         $liveuser->logout();
169     }else{
170         //Get data as we would for logging        
171         $loginData=ewiki_liveuser_get_login_data();
172         liveuser_loglogin(); 
173         //Tests login, updates $username
174         if ($username=$liveuser->getHandle()){
175             //Clear delay flags with matching handle, php session, ssl session, and ip
176             // (today only)
177             $liveuserDB->query('
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']);
187         }
188     }
189 }
190
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;
196 } else {
197     $GLOBALS['ewiki_author'] = null;
198     $GLOBALS['ewiki_auth_user'] = null;
199     define('EWIKI_AUTO_EDIT',0);
200 }            
201          
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>
212     
213     <form action="" method="post">
214     <table border="0" bgcolor="#eeeeee" cellspacing="0" cellpadding="4">
215     <tr valign="top" align="left">
216         <td>Username:</td>
217         <td><input type="text" name="username" size="32" maxlength="32"></td>
218     </tr>
219     <tr valign="top" align="left">
220         <td>Password:</td>
221         <td><input type="password" name="password" size="32" maxlength="32"></td>
222     </tr>
223     <tr>
224         <td>&nbsp;</td>
225         <td align="right">
226             <input type="submit" name="submit_login" value="Login now">
227             <input type="submit" name="cancel_login" value="Cancel">            
228         </td>
229     </tr>
230     </table>
231     </form>';
232     
233
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>
248
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>
254     </tr>
255     <tr valign="top" align="left">
256         <td>New Password:</td>
257         <td><input type="password" name="newpassword1" size="32" maxlength="32"></td>
258     </tr>
259     <tr valign="top" align="left">
260         <td>Repeat New Password:</td>
261         <td><input type="password" name="newpassword2" size="32" maxlength="32"></td>
262     </tr>
263     <tr>
264         <td>&nbsp;</td>
265         <td align=right>
266             <input type="submit" name="submit" value="Change Password">
267             <input type="button" name="cancel_pwchng" value="Cancel">            
268         </td>
269     </tr>
270     </table>
271     </form>';
272 $ewiki_t['en']['PASS_DICTIONARY_READ_ERROR'] = "<p>An error was encountered while validating your password.</p>";
273 /**
274  * processes login for the current user.
275  *
276  * @param mixed id
277  * @param mixed data
278  * @return string page output response to login attempt
279  */
280 function ewiki_page_liveuser_login($id, $data)
281 {
282     global $liveuser,$liveuser_delay_policies;
283
284     liveweb_query_delay_data($liveuser_delay_policies);
285     //var_dump($liveuser_delay_policies);
286     
287     $totalDelay=liveweb_get_total_delay($liveuser_delay_policies);
288
289     $o=ewiki_make_title($id, $id, 2);
290     if($totalDelay > EWIKI_LIVEUSER_LOGIN_SHUTDOWN_DELAY){
291         $o.=ewiki_t('LOGINSHUTDOWN');
292         return($o);
293     }else{
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'));
297         } else {
298             // all other calls, print login status or form output
299             return $o.($liveuser->isLoggedIn() ? ewiki_t('LOGGEDINALREADY') : ewiki_t('LOGINFORM'));
300         }    
301     }
302 }
303
304 /**
305  * logs out the current user.
306  *
307  * @param mixed id
308  * @param mixed data
309  * @return string page output for logout message
310  */
311 function ewiki_page_liveuser_logout($id, $data)
312 {
313     global $liveuser;
314     
315     $liveuser->logout();
316     return ewiki_make_title($id, $id, 2).ewiki_t('LOGGEDOUT');
317 }
318
319 /**
320  * changes current user's password based on form input
321  *
322  * @param mixed id
323  * @param mixed data
324  * @return mixed
325  */
326 function ewiki_page_liveuser_chpw($id, $data)
327
328     global $liveuser, $liveuserAuthAdmin;
329
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');
333     }
334         
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');
342     }
343     //$time=getmicrotime();
344     $password_status=ewiki_check_passwd($_REQUEST['newpassword1'],$liveuser->getHandle());
345     //$end=getmicrotime();
346     //echo($end-$time);
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');
350       } else {
351         return ewiki_make_title($id, $id, 2).ewiki_t($password_status).'<!--'.$password_status.'-->'.ewiki_t('CHPW_FORM');
352       }
353     }
354     
355     // return success
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');
360     } else {
361         return ewiki_make_title($id, $id, 2).ewiki_t('CHPW_ERROR');
362     }
363 }
364
365
366 /**
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. 
369  *
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
376  */
377 function ewiki_auth_query_liveuser(&$data, $force_query = 0)
378 {
379     global $liveuser, $ewiki_config, $ewiki_errmsg, $ewiki_ring;
380     
381     if ($liveuser->isLoggedIn()) {
382         $ewiki_ring = EWIKI_LOGGEDIN_RING;
383         return true;
384     } else {    
385         if ($force_query){
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');
390             } else {
391                 $ewiki_errmsg = ewiki_t('LOGINFORM');
392             }
393         }
394         
395         $liveuser->logout();
396         $ewiki_ring = EWIKI_NOT_LOGGEDIN_RING;
397         
398         return false;
399     }
400 }
401
402 //Gets general data about login for loging etc.
403 function ewiki_liveuser_get_login_data(){
404     global $liveuser;
405
406     $success=$liveuser->isLoggedIn();
407
408     $requestInfo=array(
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);
415         
416     return($requestInfo);
417 }
418
419 //logs current login
420 function liveuser_loglogin(){
421     global $liveuserDB;
422
423     if (!isset($_REQUEST['username'])){
424         return;
425     }
426     
427     $requestInfo= ewiki_liveuser_get_login_data();
428   
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'];
432     
433     //var_dump($requestInfo);
434     
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)
443         {
444             die('Failure in database connection, unable to continue');
445         }
446     //*/      
447 }
448
449 // Calculates delay, utility function
450 function liveweb_delay_calc($count, $threshold, $coeff){
451     return(max($count-$threshold,0)*$coeff);
452 }
453 function liveweb_get_total_delay($liveuser_delay_policies){
454     $delay=0;
455
456     foreach($liveuser_delay_policies as $currPolicy){
457         $delay += liveweb_delay_calc($currPolicy['count'], $currPolicy['threshold'], $currPolicy['coefficient']);
458     }
459     
460     return($delay);
461 }
462
463 //Populates 'count' with the number of respondant records
464 function liveweb_query_delay_data(&$liveuser_delay_policies){
465     global $liveuserDB;
466
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']));
470     }
471 }
472
473 //Sends alerts if need be
474 function ewiki_liveuser_IDS_alerts($liveuser_delay_policies,$username){
475     $send_alert=0;
476     foreach ($liveuser_delay_policies as $polName => $currPolicy){
477         if((++$liveuser_delay_policies[$polName]['count'])%$currPolicy['alert_mod']==0){
478             $send_alert=1;
479         }            
480     }
481             
482     if(!$send_alert){   
483       return;
484     }
485     $m_text = "";
486         
487     foreach ($liveuser_delay_policies as $polName => $currPolicy){
488         $m_text.="$polName:  {$currPolicy['count']}(mod {$currPolicy['alert_mod']})\n";
489     }
490     
491     ($server = $_SERVER["HTTP_HOST"]) or
492     ($server = $_SERVER["SERVER_NAME"]);
493     
494     $m_text.="$username attempting to login\n";
495     
496     $m_text.= "\n(".EWIKI_PAGE_INDEX." on http://$server/)\n";                
497     $m_text.=  $_SERVER["SERVER_ADMIN"]."\n";
498     
499     //$m_from = EWIKI_NOTIFY_SENDER."@$server";
500     $m_from = "Alerts@$server";
501     $m_subject = ALERT_SUBJECT;            
502     $m_to = ALERT_RECIPIENTS;
503     
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);    
506
507 }
508
509 ?>