cleanup text-match
[awl.git] / inc / Session.php
blob2859d9dba83c7ea3ad401a08f0b7cd0406642091
1 <?php
2 /**
3 * Session handling class and associated functions
5 * This subpackage provides some functions that are useful around web
6 * application session management.
8 * The class is intended to be as lightweight as possible while holding
9 * all session data in the database:
10 * - Session hash is not predictable.
11 * - No clear text information is held in cookies.
12 * - Passwords are generally salted MD5 hashes, but individual users may
13 * have plain text passwords set by an administrator.
14 * - Temporary passwords are supported.
15 * - Logout is supported
16 * - "Remember me" cookies are supported, and will result in a new
17 * Session for each browser session.
19 * @package awl
20 * @subpackage Session
21 * @author Andrew McMillan <andrew@mcmillan.net.nz>
22 * @copyright Catalyst IT Ltd, Morphoss Ltd <http://www.morphoss.com/>
23 * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
25 require_once('AWLUtilities.php');
26 require_once('AwlQuery.php');
27 require_once('EMail.php');
30 /**
31 * Checks what a user entered against any currently valid temporary passwords on their account.
32 * @param string $they_sent What the user entered.
33 * @param int $user_no Which user is attempting to log on.
34 * @return boolean Whether or not the user correctly guessed a temporary password within the necessary window of opportunity.
36 function check_temporary_passwords( $they_sent, $user_no ) {
37 $sql = 'SELECT 1 AS ok FROM tmp_password WHERE user_no = ? AND password = ? AND valid_until > current_timestamp';
38 $qry = new AwlQuery( $sql, $user_no, $they_sent );
39 if ( $qry->Exec('Session::check_temporary_passwords') ) {
40 dbg_error_log( "Login", " check_temporary_passwords: Rows = ".$qry->rows());
41 if ( $row = $qry->Fetch() ) {
42 dbg_error_log( "Login", " check_temporary_passwords: OK = $row->ok");
43 // Remove all the temporary passwords for that user...
44 $sql = 'DELETE FROM tmp_password WHERE user_no = ? ';
45 $qry = new AwlQuery( $sql, $user_no );
46 $qry->Exec('Login',__LINE__,__FILE__);
47 return true;
50 return false;
53 /**
54 * A class for creating and holding session information.
56 * @package awl
58 class Session
60 /**#@+
61 * @access private
63 var $roles;
64 var $cause = '';
65 /**#@-*/
67 /**#@+
68 * @access public
71 /**
72 * The user_no of the logged in user.
73 * @var int
75 var $user_no;
77 /**
78 * A unique id for this user's logged-in session.
79 * @var int
81 var $session_id = 0;
83 /**
84 * The user's username used to log in.
85 * @var int
87 var $username = 'guest';
89 /**
90 * The user's full name from their usr record.
91 * @var int
93 var $fullname = 'Guest';
95 /**
96 * The user's email address from their usr record.
97 * @var int
99 var $email = '';
102 * Whether this user has actually logged in.
103 * @var int
105 var $logged_in = false;
108 * Whether the user logged in to view the current page. Perhaps some details on the
109 * login form might pollute an editable form and result in an unplanned submit. This
110 * can be used to program around such a problem.
111 * @var boolean
113 var $just_logged_in = false;
116 * The date and time that the user logged on during their last session.
117 * @var string
119 var $last_session_start;
122 * The date and time that the user requested their last page during their last
123 * session.
124 * @var string
126 var $last_session_end;
127 /**#@-*/
130 * Create a new Session object.
132 * If a session identifier is supplied, or we can find one in a cookie, we validate it
133 * and consider the person logged in. We read some useful session and user data in
134 * passing as we do this.
136 * The session identifier contains a random value, hashed, to provide validation. This
137 * could be hijacked if the traffic was sniffable so sites who are paranoid about security
138 * should only do this across SSL.
140 * A worthwhile enhancement would be to add some degree of external configurability to
141 * that read.
143 * @param string $sid A session identifier.
145 function Session( $sid="" )
147 global $sid, $sysname;
149 $this->roles = array();
150 $this->logged_in = false;
151 $this->just_logged_in = false;
152 $this->login_failed = false;
154 if ( $sid == "" ) {
155 if ( ! isset($_COOKIE['sid']) ) return;
156 $sid = $_COOKIE['sid'];
159 list( $session_id, $session_key ) = explode( ';', $sid, 2 );
162 * We regularly want to override the SQL for joining against the session record.
163 * so the calling application can define a function local_session_sql() which
164 * will return the SQL to join (up to and excluding the WHERE clause. The standard
165 * SQL used if this function is not defined is:
166 * <code>
167 * SELECT session.*, usr.* FROM session JOIN usr ON ( user_no )
168 * </code>
170 if ( function_exists('local_session_sql') ) {
171 $sql = local_session_sql();
173 else {
174 $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
176 $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
178 $qry = new AwlQuery($sql, $session_id, $session_key, $session_key);
179 if ( $qry->Exec('Session') && 1 == $qry->rows() ) {
180 $this->AssignSessionDetails( $qry->Fetch() );
181 $qry = new AwlQuery('UPDATE session SET session_end = current_timestamp WHERE session_id=?', $session_id);
182 $qry->Exec('Session');
184 else {
185 // Kill the existing cookie, which appears to be bogus
186 setcookie('sid', '', 0,'/');
187 $this->cause = 'ERR: Other than one session record matches. ' . $qry->rows();
188 $this->Log( "WARN: Login $this->cause" );
194 * DEPRECATED Utility function to log stuff with printf expansion.
196 * This function could be expanded to log something identifying the session, but
197 * somewhat strangely this has not yet been done.
199 * @param string $whatever A log string
200 * @param mixed $whatever... Further parameters to be replaced into the log string a la printf
202 function Log( $whatever )
204 global $c;
206 $argc = func_num_args();
207 $format = func_get_arg(0);
208 if ( $argc == 1 || ($argc == 2 && func_get_arg(1) == "0" ) ) {
209 error_log( "$c->sysabbr: $format" );
211 else {
212 $args = array();
213 for( $i=1; $i < $argc; $i++ ) {
214 $args[] = func_get_arg($i);
216 error_log( "$c->sysabbr: " . vsprintf($format,$args) );
221 * DEPRECATED Utility function to log debug stuff with printf expansion, and the ability to
222 * enable it selectively.
224 * The enabling is done by setting a variable "$debuggroups[$group] = 1"
226 * @param string $group The name of an arbitrary debug group.
227 * @param string $whatever A log string
228 * @param mixed $whatever... Further parameters to be replaced into the log string a la printf
230 function Dbg( $whatever )
232 global $debuggroups, $c;
234 $argc = func_num_args();
235 $dgroup = func_get_arg(0);
237 if ( ! (isset($debuggroups[$dgroup]) && $debuggroups[$dgroup]) ) return;
239 $format = func_get_arg(1);
240 if ( $argc == 2 || ($argc == 3 && func_get_arg(2) == "0" ) ) {
241 error_log( "$c->sysabbr: DBG: $dgroup: $format" );
243 else {
244 $args = array();
245 for( $i=2; $i < $argc; $i++ ) {
246 $args[] = func_get_arg($i);
248 error_log( "$c->sysabbr: DBG: $dgroup: " . vsprintf($format,$args) );
253 * Checks whether a user is allowed to do something.
255 * The check is performed to see if the user has that role.
257 * @param string $whatever The role we want to know if the user has.
258 * @return boolean Whether or not the user has the specified role.
260 function AllowedTo ( $whatever ) {
261 return ( $this->logged_in && isset($this->roles[$whatever]) && $this->roles[$whatever] );
266 * Internal function used to get the user's roles from the database.
268 function GetRoles () {
269 $this->roles = array();
270 $qry = new AwlQuery( 'SELECT role_name FROM role_member m join roles r ON r.role_no = m.role_no WHERE user_no = ? ', $this->user_no );
271 if ( $qry->Exec('Session::GetRoles') && $qry->rows() > 0 ) {
272 while( $role = $qry->Fetch() ) {
273 $this->roles[$role->role_name] = true;
280 * Internal function used to assign the session details to a user's new session.
281 * @param object $u The user+session object we (probably) read from the database.
283 function AssignSessionDetails( $u ) {
284 // Assign each field in the selected record to the object
285 foreach( $u AS $k => $v ) {
286 $this->{$k} = $v;
289 $date_format = ($this->date_format_type == 'E' ? 'European,ISO' : ($this->date_format_type == 'U' ? 'US,ISO' : 'ISO'));
290 $qry = new AwlQuery( 'SET DATESTYLE TO '. $date_format );
291 $qry->Exec();
293 $this->GetRoles();
294 $this->logged_in = true;
299 * Attempt to perform a login action.
301 * This will validate the user's username and password. If they are OK then a new
302 * session id will be created and the user will be cookied with it for subsequent
303 * pages. A logged in session will be created, and the $_POST array will be cleared
304 * of the username, password and submit values. submit will also be cleared from
305 * $_GET and $GLOBALS, just in case.
307 * @param string $username The user's login name, or at least what they entered it as.
308 * @param string $password The user's password, or at least what they entered it as.
309 * @param string $authenticated If true, then authentication has already happened and the password is not checked, though the user must still exist.
310 * @return boolean Whether or not the user correctly guessed a temporary password within the necessary window of opportunity.
312 function Login( $username, $password, $authenticated = false ) {
313 global $c;
314 $rc = false;
315 dbg_error_log( "Login", " Login: Attempting login for $username" );
316 if ( isset($usr) ) unset($usr); /** In case someone is running with register_globals on */
319 * @todo In here we will need to put code to call the auth plugin, in order to
320 * ensure the 'usr' table has current valid data. At this stage we are just
321 * thinking it through... like ...
324 if ( !$authenticated && isset($c->authenticate_hook) && isset($c->authenticate_hook['call']) && function_exists($c->authenticate_hook['call']) ) {
326 * The authenticate hook needs to:
327 * - Accept a username / password
328 * - Confirm the username / password are correct
329 * - Create (or update) a 'usr' record in our database
330 * - Return the 'usr' record as an object
331 * - Return === false when authentication fails
332 * It can expect that:
333 * - Configuration data will be in $c->authenticate_hook['config'], which might be an array, or whatever is needed.
335 $usr = call_user_func( $c->authenticate_hook['call'], $username, $password );
336 if ( $usr === false ) unset($usr); else $authenticated = true;
339 $sql = "SELECT * FROM usr WHERE lower(username) = text(?) AND active";
340 $qry = new AwlQuery( $sql, strtolower($username) );
341 if ( isset($usr) || ($qry->Exec('Login',__LINE__,__FILE__) && $qry->rows() == 1 && $usr = $qry->Fetch() ) ) {
342 $user_no = ( method_exists( $usr, 'user_no' ) ? $usr->user_no() : $usr->user_no );
343 if ( $authenticated || session_validate_password( $password, $usr->password ) || check_temporary_passwords( $password, $user_no ) ) {
344 // Now get the next session ID to create one from...
345 $qry = new AwlQuery( "SELECT nextval('session_session_id_seq')" );
346 if ( $qry->Exec('Login') && $qry->rows() == 1 ) {
347 $seq = $qry->Fetch();
348 $session_id = $seq->nextval;
349 $session_key = md5( rand(1010101,1999999999) . microtime() ); // just some random shite
350 dbg_error_log( "Login", " Login: Valid username/password for $username ($user_no)" );
352 // Set the last_used timestamp to match the previous login.
353 $qry = new AwlQuery('UPDATE usr SET last_used = (SELECT session_start FROM session WHERE session.user_no = ? ORDER BY session_id DESC LIMIT 1) WHERE user_no = ?;', $usr->user_no, $usr->user_no);
354 $qry->Exec('Session');
356 // And create a session
357 $sql = "INSERT INTO session (session_id, user_no, session_key) VALUES( ?, ?, ? )";
358 $qry = new AwlQuery( $sql, $session_id, $user_no, $session_key );
359 if ( $qry->Exec('Login') ) {
360 // Assign our session ID variable
361 $sid = "$session_id;$session_key";
363 // Create a cookie for the sesssion
364 setcookie('sid',$sid, 0,'/');
365 // Recognise that we have started a session now too...
366 $this->Session($sid);
367 dbg_error_log( "Login", " Login: New session $session_id started for $username ($user_no)" );
368 if ( isset($_POST['remember']) && intval($_POST['remember']) > 0 ) {
369 $cookie = md5( $user_no ) . ";";
370 $cookie .= session_salted_md5($user_no . $usr->username . $usr->password);
371 $GLOBALS['lsid'] = $cookie;
372 setcookie( "lsid", $cookie, time() + (86400 * 3600), "/" ); // will expire in ten or so years
374 $this->just_logged_in = true;
376 // Unset all of the submitted values, so we don't accidentally submit an unexpected form.
377 unset($_POST['username']);
378 unset($_POST['password']);
379 unset($_POST['submit']);
380 unset($_GET['submit']);
381 unset($GLOBALS['submit']);
383 if ( function_exists('local_session_sql') ) {
384 $sql = local_session_sql();
386 else {
387 $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
389 $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
391 $qry = new AwlQuery($sql, $session_id, $session_key, $session_key);
392 if ( $qry->Exec('Session') && 1 == $qry->rows() ) {
393 $this->AssignSessionDetails( $qry->Fetch() );
396 $rc = true;
397 return $rc;
399 // else ...
400 $this->cause = 'ERR: Could not create new session.';
402 else {
403 $this->cause = 'ERR: Could not increment session sequence.';
406 else {
407 $c->messages[] = i18n('Invalid username or password.');
408 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
409 $this->cause = 'WARN: Invalid password.';
410 else
411 $this->cause = 'WARN: Invalid username or password.';
414 else {
415 $c->messages[] = i18n('Invalid username or password.');
416 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
417 $this->cause = 'WARN: Invalid username.';
418 else
419 $this->cause = 'WARN: Invalid username or password.';
422 $this->Log( "Login failure: $this->cause" );
423 $this->login_failed = true;
424 $rc = false;
425 return $rc;
431 * Attempts to logs in using a long-term session ID
433 * This is all horribly insecure, but its hard not to be.
435 * @param string $lsid The user's value of the lsid cookie.
436 * @return boolean Whether or not the user's lsid cookie got them in the door.
438 function LSIDLogin( $lsid ) {
439 global $c;
440 dbg_error_log( "Login", " LSIDLogin: Attempting login for $lsid" );
442 list($md5_user_no,$validation_string) = explode( ';', $lsid );
443 $qry = new AwlQuery( "SELECT * FROM usr WHERE md5(user_no::text)=? AND active", $md5_user_no );
444 if ( $qry->Exec('Login') && $qry->rows() == 1 ) {
445 $usr = $qry->Fetch();
446 list( $x, $salt, $y) = explode('*', $validation_string);
447 $my_validation = session_salted_md5($usr->user_no . $usr->username . $usr->password, $salt);
448 if ( $validation_string == $my_validation ) {
449 // Now get the next session ID to create one from...
450 $qry = new AwlQuery( "SELECT nextval('session_session_id_seq')" );
451 if ( $qry->Exec('Login') && $qry->rows() == 1 ) {
452 $seq = $qry->Fetch();
453 $session_id = $seq->nextval;
454 $session_key = md5( rand(1010101,1999999999) . microtime() ); // just some random shite
455 dbg_error_log( "Login", " LSIDLogin: Valid username/password for $username ($usr->user_no)" );
457 // And create a session
458 $sql = "INSERT INTO session (session_id, user_no, session_key) VALUES( ?, ?, ? )";
459 $qry = new AwlQuery( $sql, $session_id, $usr->user_no, $session_key );
460 if ( $qry->Exec('Login') ) {
461 // Assign our session ID variable
462 $sid = "$session_id;$session_key";
464 // Create a cookie for the sesssion
465 setcookie('sid',$sid, 0,'/');
466 // Recognise that we have started a session now too...
467 $this->Session($sid);
468 dbg_error_log( "Login", " LSIDLogin: New session $session_id started for $this->username ($usr->user_no)" );
470 $this->just_logged_in = true;
472 // Unset all of the submitted values, so we don't accidentally submit an unexpected form.
473 unset($_POST['username']);
474 unset($_POST['password']);
475 unset($_POST['submit']);
476 unset($_GET['submit']);
477 unset($GLOBALS['submit']);
479 if ( function_exists('local_session_sql') ) {
480 $sql = local_session_sql();
482 else {
483 $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
485 $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
487 $qry = new AwlQuery($sql, $session_id, $session_key, $session_key);
488 if ( $qry->Exec('Session') && 1 == $qry->rows() ) {
489 $this->AssignSessionDetails( $qry->Fetch() );
492 $rc = true;
493 return $rc;
495 // else ...
496 $this->cause = 'ERR: Could not create new session.';
498 else {
499 $this->cause = 'ERR: Could not increment session sequence.';
502 else {
503 dbg_error_log( "Login", " LSIDLogin: $validation_string != $my_validation ($salt - $usr->user_no, $usr->username, $usr->password)");
504 $client_messages[] = i18n('Invalid username or password.');
505 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
506 $this->cause = 'WARN: Invalid password.';
507 else
508 $this->cause = 'WARN: Invalid username or password.';
511 else {
512 $client_messages[] = i18n('Invalid username or password.');
513 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
514 $this->cause = 'WARN: Invalid username.';
515 else
516 $this->cause = 'WARN: Invalid username or password.';
519 dbg_error_log( "Login", " LSIDLogin: $this->cause" );
520 return false;
525 * Renders some HTML for a basic login panel
527 * @return string The HTML to display a login panel.
529 function RenderLoginPanel() {
530 $action_target = htmlspecialchars(preg_replace('/\?logout.*$/','',$_SERVER['REQUEST_URI']));
531 dbg_error_log( "Login", " RenderLoginPanel: action_target='%s'", $action_target );
532 $userprompt = translate("User Name");
533 $pwprompt = translate("Password");
534 $rememberprompt = str_replace( ' ', '&nbsp;', translate("forget me not"));
535 $gobutton = htmlspecialchars(translate("GO!"));
536 $gotitle = htmlspecialchars(translate("Enter your username and password then click here to log in."));
537 $temppwprompt = translate("If you have forgotten your password then");
538 $temppwbutton = htmlspecialchars(translate("Help! I've forgotten my password!"));
539 $temppwtitle = htmlspecialchars(translate("Enter a username, if you know it, and click here, to be e-mailed a temporary password."));
540 $html = <<<EOTEXT
541 <div id="logon">
542 <form action="$action_target" method="post">
543 <table>
544 <tr>
545 <th class="prompt">$userprompt:</th>
546 <td class="entry">
547 <input class="text" type="text" name="username" size="12" /></td>
548 </tr>
549 <tr>
550 <th class="prompt">$pwprompt:</th>
551 <td class="entry">
552 <input class="password" type="password" name="password" size="12" />
553 &nbsp;<label>$rememberprompt: <input class="checkbox" type="checkbox" name="remember" value="1" /></label>
554 </td>
555 </tr>
556 <tr>
557 <th class="prompt">&nbsp;</th>
558 <td class="entry">
559 <input type="submit" value="$gobutton" title="$gotitle" name="submit" class="submit" />
560 </td>
561 </tr>
562 </table>
564 $temppwprompt: <input type="submit" value="$temppwbutton" title="$temppwtitle" name="lostpass" class="submit" />
565 </p>
566 </form>
567 </div>
569 EOTEXT;
570 return $html;
575 * Checks that this user is logged in, and presents a login screen if they aren't.
577 * The function can optionally confirm whether they are a member of one of a list
578 * of groups, and deny access if they are not a member of any of them.
580 * @param string $groups The list of groups that the user must be a member of one of to be allowed to proceed.
581 * @return boolean Whether or not the user is logged in and is a member of one of the required groups.
583 function LoginRequired( $groups = "" ) {
584 global $c, $session, $page_elements;
586 if ( $this->logged_in && $groups == "" ) return;
587 if ( ! $this->logged_in ) {
588 // $c->messages[] = i18n("You must log in to use this system.");
589 if ( function_exists("local_index_not_logged_in") ) {
590 local_index_not_logged_in();
592 else {
593 $login_html = translate( "<h1>Log On Please</h1><p>For access to the %s you should log on withthe username and password that have been issued to you.</p><p>If you would like to request access, please e-mail %s.</p>");
594 $page_content = sprintf( $login_html, $c->system_name, $c->admin_email );
595 $page_content .= $this->RenderLoginPanel();
596 if ( isset($page_elements) && gettype($page_elements) == 'array' ) {
597 $page_elements[] = $page_content;
598 @include("page-renderer.php");
599 exit(0);
601 @include("page-header.php");
602 echo $page_content;
603 @include("page-footer.php");
606 else {
607 $valid_groups = explode(",", $groups);
608 foreach( $valid_groups AS $k => $v ) {
609 if ( $this->AllowedTo($v) ) return;
611 $c->messages[] = i18n("You are not authorised to use this function.");
612 if ( isset($page_elements) && gettype($page_elements) == 'array' ) {
613 @include("page-renderer.php");
614 exit(0);
616 @include("page-header.php");
617 @include("page-footer.php");
620 exit;
626 * E-mails a temporary password in response to a request from a user.
628 * This could be called from somewhere within the application that allows
629 * someone to set up a user and invite them.
631 * This function includes EMail.php to actually send the password.
633 function EmailTemporaryPassword( $username, $email_address, $body_template="" ) {
634 global $c;
636 $password_sent = false;
637 $where = "";
638 $params = array();
639 if ( isset($username) && $username != "" ) {
640 $where = 'WHERE active AND lower(usr.username) = :lcusername';
641 $params[':lcusername'] = strtolower($username);
643 else if ( isset($email_address) && $email_address != "" ) {
644 $where = 'WHERE active AND lower(usr.email) = :lcemail';
645 $params[':lcemail'] = strtolower($email_address);
648 if ( $where != '' ) {
649 if ( !isset($body_template) || $body_template == "" ) {
650 $body_template = <<<EOTEXT
652 @@debugging@@A temporary password has been requested for @@system_name@@.
654 Temporary Password: @@password@@
656 This has been applied to the following usernames:
658 @@usernames@@
659 and will be valid for 24 hours.
661 If you have any problems, please contact the system administrator.
663 EOTEXT;
666 $qry = new AwlQuery( 'SELECT * FROM usr '.$where, $params );
667 $qry->Exec('Session::EmailTemporaryPassword');
668 if ( $qry->rows() > 0 ) {
669 $q2 = new AwlQuery();
670 $q2->Begin();
672 while ( $row = $qry->Fetch() ) {
673 $mail = new EMail( "Access to $c->system_name" );
674 $mail->SetFrom($c->admin_email );
675 $usernames = "";
676 $debug_to = "";
677 if ( isset($c->debug_email) ) {
678 $debug_to = "This e-mail would normally be sent to:\n ";
679 $mail->AddTo( "Tester <$c->debug_email>" );
682 $tmp_passwd = '';
683 for ( $i=0; $i < 8; $i++ ) {
684 $tmp_passwd .= substr( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ+#.-=*%@0123456789abcdefghijklmnopqrstuvwxyz', rand(0,69), 1);
687 $q2->QDo('INSERT INTO tmp_password (user_no, password) VALUES(?,?)', array($row->user_no, $tmp_passwd));
688 if ( isset($c->debug_email) ) {
689 $debug_to .= "$row->fullname <$row->email> ";
691 else {
692 $mail->AddTo( "$row->fullname <$row->email>" );
694 $usernames .= " $row->username\n";
696 if ( $mail->To() != "" ) {
697 if ( isset($c->debug_email) ) {
698 $debug_to .= "\n============================================================\n";
700 $sql .= "COMMIT;";
701 $qry = new AwlQuery( $sql );
702 $qry->Exec("Session::SendTemporaryPassword");
703 $body = str_replace( '@@system_name@@', $c->system_name, $body_template);
704 $body = str_replace( '@@password@@', $tmp_passwd, $body);
705 $body = str_replace( '@@usernames@@', $usernames, $body);
706 $body = str_replace( '@@debugging@@', $debug_to, $body);
707 $mail->SetBody($body);
708 $mail->Send();
709 $password_sent = true;
714 return $password_sent;
719 * Sends a temporary password in response to a request from a user.
721 * This is probably only going to be called from somewhere internal. An external
722 * caller will probably just want the e-mail, without the HTML that this displays.
725 function SendTemporaryPassword( ) {
726 global $c, $page_elements;
728 $password_sent = $this->EmailTemporaryPassword( (isset($_POST['username'])?$_POST['username']:null), (isset($_POST['email_address'])?$_POST['email_address']:null) );
730 if ( ! $password_sent && ((isset($_POST['username']) && $_POST['username'] != "" )
731 || (isset($_POST['email_address']) && $_POST['email_address'] != "" )) ) {
732 // Username or EMail were non-null, but we didn't find that user.
734 $page_content = <<<EOTEXT
735 <div id="logon">
736 <h1>Unable to Reset Password</h1>
737 <p>We were unable to reset your password at this time. Please contact
738 <a href="mailto:$c->admin_email">$c->admin_email</a>
739 to arrange for an administrator to reset your password.</p>
740 <p>Thank you.</p>
741 </div>
742 EOTEXT;
745 if ( $password_sent ) {
746 $page_content = <<<EOTEXT
747 <div id="logon">
748 <h1>Temporary Password Sent</h1>
749 <p>A temporary password has been e-mailed to you. This password
750 will be valid for 24 hours and you will be required to change
751 your password after logging in.</p>
752 <p><a href="/">Click here to return to the login page.</a></p>
753 </div>
754 EOTEXT;
756 else {
757 $page_content = <<<EOTEXT
758 <div id="logon">
759 <h1>Temporary Password</h1>
760 <form action="$action_target" method="post">
761 <table>
762 <tr>
763 <th class="prompt" style="white-space: nowrap;">Enter your User Name:</th>
764 <td class="entry"><input class="text" type="text" name="username" size="12" /></td>
765 </tr>
766 <tr>
767 <th class="prompt" style="white-space: nowrap;">Or your EMail Address:</th>
768 <td class="entry"><input class="text" type="text" name="email_address" size="50" /></td>
769 </tr>
770 <tr>
771 <th class="prompt" style="white-space: nowrap;">and click on -></th>
772 <td class="entry">
773 <input class="submit" type="submit" value="Send me a temporary password" alt="Enter a username, or e-mail address, and click here." name="lostpass" />
774 </td>
775 </tr>
776 </table>
777 <p>Note: If you have multiple accounts with the same e-mail address, they will <em>all</em>
778 be assigned a new temporary password, but only the one(s) that you use that temporary password
779 on will have the existing password invalidated.</p>
780 <h2>The temporary password will only be valid for 24 hours.</h2>
781 <p>You will need to log on and change your password during this time.</p>
782 </form>
783 </div>
784 EOTEXT;
786 if ( isset($page_elements) && gettype($page_elements) == 'array' ) {
787 $page_elements[] = $page_content;
788 @include("page-renderer.php");
789 exit(0);
791 @include("page-header.php");
792 echo $page_content;
793 @include("page-footer.php");
794 exit(0);
797 static function _CheckLogout() {
798 if ( isset($_GET['logout']) ) {
799 dbg_error_log( "Login", ":_CheckLogout: Logging out");
800 setcookie( 'sid', '', 0,'/');
801 unset($_COOKIE['sid']);
802 unset($GLOBALS['sid']);
803 unset($_COOKIE['lsid']); // Allow a cookied person to be un-logged-in for one page view.
804 unset($GLOBALS['lsid']);
806 if ( isset($_GET['forget']) ) setcookie( 'lsid', '', 0,'/');
810 function _CheckLogin() {
811 global $c;
812 if ( isset($_POST['lostpass']) ) {
813 dbg_error_log( "Login", ":_CheckLogin: User '$_POST[username]' has lost the password." );
814 $this->SendTemporaryPassword();
816 else if ( isset($_POST['username']) && isset($_POST['password']) ) {
817 // Try and log in if we have a username and password
818 $this->Login( $_POST['username'], $_POST['password'] );
819 @dbg_error_log( "Login", ":_CheckLogin: User %s(%s) - %s (%d) login status is %d", $_POST['username'], $this->fullname, $this->user_no, $this->logged_in );
821 else if ( !isset($_COOKIE['sid']) && isset($_COOKIE['lsid']) && $_COOKIE['lsid'] != "" ) {
822 // Validate long-term session details
823 $this->LSIDLogin( $_COOKIE['lsid'] );
824 dbg_error_log( "Login", ":_CheckLogin: User $this->username - $this->fullname ($this->user_no) login status is $this->logged_in" );
826 else if ( !isset($_COOKIE['sid']) && isset($c->authenticate_hook['server_auth_type']) ) {
828 * The authentication should have happened in the server, and we should accept it if so.
830 if ( is_array($c->authenticate_hook['server_auth_type']) ) {
831 if ( in_array( strtolower($_SERVER['AUTH_TYPE']), array_map('strtolower', $c->authenticate_hook['server_auth_type']) )) {
832 if (isset($_SERVER["REMOTE_USER"]))
833 $this->Login($_SERVER['REMOTE_USER'], "", true); // Password will not be checked.
834 else
835 $this->Login($_SERVER['REDIRECT_REMOTE_USER'], "", true); // Password will not be checked.
838 else if ( strtolower($c->authenticate_hook['server_auth_type']) == strtolower($_SERVER['AUTH_TYPE']) ) {
840 * Perhaps this 'split' is not a good idea though. People may want to use the
841 * full ID as the username. A further option may be desirable.
843 if (isset($_SERVER["REMOTE_USER"]))
844 list($username) = explode('@', $_SERVER['REMOTE_USER']);
845 else
846 list($username) = explode('@', $_SERVER['REDIRECT_REMOTE_USER']);
847 $this->Login($username, "", true); // Password will not be checked.
854 * Function to reformat an ISO date to something nicer and possibly more localised
855 * @param string $indate The ISO date to be formatted.
856 * @param string $type If 'timestamp' then the time will also be shown.
857 * @return string The nicely formatted date.
859 function FormattedDate( $indate, $type='date' ) {
860 $out = "";
861 if ( preg_match( '#^\s*$#', $indate ) ) {
862 // Looks like it's empty - just return empty
863 return $indate;
865 if ( preg_match( '#^\d{1,2}[/-]\d{1,2}[/-]\d{2,4}#', $indate ) ) {
866 // Looks like it's nice already - don't screw with it!
867 return $indate;
869 $yr = substr($indate,0,4);
870 $mo = substr($indate,5,2);
871 $dy = substr($indate,8,2);
872 switch ( $this->date_format_type ) {
873 case 'U':
874 $out = sprintf( "%d/%d/%d", $mo, $dy, $yr );
875 break;
876 case 'E':
877 $out = sprintf( "%d/%d/%d", $dy, $mo, $yr );
878 break;
879 default:
880 $out = sprintf( "%d-%02d-%02d", $yr, $mo, $dy );
881 break;
883 if ( $type == 'timestamp' ) {
884 $out .= substr($indate,10,6);
886 return $out;
891 * Build a hash which we can use for confirmation that we didn't get e-mailed
892 * a bogus link by someone, and that we actually got here by traversing the
893 * website.
895 * @param string $method Either 'GET' or 'POST' depending on the way we will use this.
896 * @param string $varname The name of the variable which we will confirm
897 * @return string A string we can use as either a GET or POST value (i.e. a hidden field, or a varname=hash pair.
899 function BuildConfirmationHash( $method, $varname ) {
901 * We include session_start in this because it is never passed to the client
902 * and since it includes microseconds would be very hard to predict.
904 $confirmation_hash = session_salted_md5( $this->session_start.$varname.$this->session_key, "" );
905 if ( $method == 'GET' ) {
906 $confirm = $varname .'='. urlencode($confirmation_hash);
908 else {
909 $confirm = sprintf( '<input type="hidden" name="%s" value="%s">', $varname, htmlspecialchars($confirmation_hash) );
911 return $confirm;
916 * Check a hash which we created through BuildConfirmationHash
918 * @param string $method Either 'GET' or 'POST' depending on the way we will use this.
919 * @param string $varname The name of the variable which we will confirm
920 * @return string A string we can use as either a GET or POST value (i.e. a hidden field, or a varname=hash pair.
922 function CheckConfirmationHash( $method, $varname ) {
923 if ( $method == 'GET' && isset($_GET[$varname])) {
924 $hashwegot = $_GET[$varname];
925 dbg_error_log('Session',':CheckConfirmationHash: We got "%s" from GET', $hashwegot );
927 else if ( isset($_POST[$varname]) ) {
928 $hashwegot = $_POST[$varname];
929 dbg_error_log('Session',':CheckConfirmationHash: We got "%s" from POST', $hashwegot );
931 else {
932 return false;
935 if ( preg_match('{^\*(.+)\*.+$}i', $hashwegot, $regs ) ) {
936 // A nicely salted md5sum like "*<salt>*<salted_md5>"
937 $salt = $regs[1];
938 dbg_error_log('Session',':CheckConfirmationHash: Salt "%s"', $salt );
939 $test_against = session_salted_md5( $this->session_start.$varname.$this->session_key, $salt ) ;
940 dbg_error_log('Session',':CheckConfirmationHash: Testing against "%s"', $test_against );
942 return ($hashwegot == $test_against);
944 return false;
951 * @global resource $session
952 * @name $session
953 * The session object is global.
956 if ( !isset($session) ) {
957 Session::_CheckLogout();
958 $session = new Session();
959 $session->_CheckLogin();