Should not depend on essential (bash) unless a specific version is
[awl.git] / inc / Session.php
blobe6350e401ad1a4031d9f687de7416fd48ab70963
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@catalyst.net.nz>
22 * @copyright Catalyst IT Ltd
23 * @license http://gnu.org/copyleft/gpl.html GNU GPL v2
25 require_once("AWLUtilities.php");
27 /**
28 * All session data is held in the database.
30 require_once('PgQuery.php');
33 /**
34 * Checks what a user entered against any currently valid temporary passwords on their account.
35 * @param string $they_sent What the user entered.
36 * @param int $user_no Which user is attempting to log on.
37 * @return boolean Whether or not the user correctly guessed a temporary password within the necessary window of opportunity.
39 function check_temporary_passwords( $they_sent, $user_no ) {
40 $sql = 'SELECT 1 AS ok FROM tmp_password WHERE user_no = ? AND password = ? AND valid_until > current_timestamp';
41 $qry = new PgQuery( $sql, $user_no, $they_sent );
42 if ( $qry->Exec('Session::check_temporary_passwords') ) {
43 dbg_error_log( "Login", " check_temporary_passwords: Rows = $qry->rows");
44 if ( $row = $qry->Fetch() ) {
45 dbg_error_log( "Login", " check_temporary_passwords: OK = $row->ok");
46 // Remove all the temporary passwords for that user...
47 $sql = 'DELETE FROM tmp_password WHERE user_no = ? ';
48 $qry = new PgQuery( $sql, $user_no );
49 $qry->Exec('Login',__LINE__,__FILE__);
50 return true;
53 return false;
56 /**
57 * A class for creating and holding session information.
59 * @package awl
61 class Session
63 /**#@+
64 * @access private
66 var $roles;
67 var $cause = '';
68 /**#@-*/
70 /**#@+
71 * @access public
74 /**
75 * The user_no of the logged in user.
76 * @var int
78 var $user_no;
80 /**
81 * A unique id for this user's logged-in session.
82 * @var int
84 var $session_id = 0;
86 /**
87 * The user's username used to log in.
88 * @var int
90 var $username = 'guest';
92 /**
93 * The user's full name from their usr record.
94 * @var int
96 var $fullname = 'Guest';
98 /**
99 * The user's email address from their usr record.
100 * @var int
102 var $email = '';
105 * Whether this user has actually logged in.
106 * @var int
108 var $logged_in = false;
111 * Whether the user logged in to view the current page. Perhaps some details on the
112 * login form might pollute an editable form and result in an unplanned submit. This
113 * can be used to program around such a problem.
114 * @var boolean
116 var $just_logged_in = false;
119 * The date and time that the user logged on during their last session.
120 * @var string
122 var $last_session_start;
125 * The date and time that the user requested their last page during their last
126 * session.
127 * @var string
129 var $last_session_end;
130 /**#@-*/
133 * Create a new Session object.
135 * If a session identifier is supplied, or we can find one in a cookie, we validate it
136 * and consider the person logged in. We read some useful session and user data in
137 * passing as we do this.
139 * The session identifier contains a random value, hashed, to provide validation. This
140 * could be hijacked if the traffic was sniffable so sites who are paranoid about security
141 * should only do this across SSL.
143 * A worthwhile enhancement would be to add some degree of external configurability to
144 * that read.
146 * @param string $sid A session identifier.
148 function Session( $sid="" )
150 global $sid, $sysname;
152 $this->roles = array();
153 $this->logged_in = false;
154 $this->just_logged_in = false;
155 $this->login_failed = false;
157 if ( $sid == "" ) {
158 if ( ! isset($_COOKIE['sid']) ) return;
159 $sid = $_COOKIE['sid'];
162 list( $session_id, $session_key ) = explode( ';', $sid, 2 );
165 * We regularly want to override the SQL for joining against the session record.
166 * so the calling application can define a function local_session_sql() which
167 * will return the SQL to join (up to and excluding the WHERE clause. The standard
168 * SQL used if this function is not defined is:
169 * <code>
170 * SELECT session.*, usr.* FROM session JOIN usr ON ( user_no )
171 * </code>
173 if ( function_exists('local_session_sql') ) {
174 $sql = local_session_sql();
176 else {
177 $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
179 $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
181 $qry = new PgQuery($sql, $session_id, $session_key, $session_key);
182 if ( $qry->Exec('Session') && 1 == $qry->rows ) {
183 $this->AssignSessionDetails( $qry->Fetch() );
184 $qry = new PgQuery('UPDATE session SET session_end = current_timestamp WHERE session_id=?', $session_id);
185 $qry->Exec('Session');
187 else {
188 // Kill the existing cookie, which appears to be bogus
189 setcookie('sid', '', 0,'/');
190 $this->cause = 'ERR: Other than one session record matches. ' . $qry->rows;
191 $this->Log( "WARN: Login $this->cause" );
197 * DEPRECATED Utility function to log stuff with printf expansion.
199 * This function could be expanded to log something identifying the session, but
200 * somewhat strangely this has not yet been done.
202 * @param string $whatever A log string
203 * @param mixed $whatever... Further parameters to be replaced into the log string a la printf
205 function Log( $whatever )
207 global $c;
209 $argc = func_num_args();
210 $format = func_get_arg(0);
211 if ( $argc == 1 || ($argc == 2 && func_get_arg(1) == "0" ) ) {
212 error_log( "$c->sysabbr: $format" );
214 else {
215 $args = array();
216 for( $i=1; $i < $argc; $i++ ) {
217 $args[] = func_get_arg($i);
219 error_log( "$c->sysabbr: " . vsprintf($format,$args) );
224 * DEPRECATED Utility function to log debug stuff with printf expansion, and the ability to
225 * enable it selectively.
227 * The enabling is done by setting a variable "$debuggroups[$group] = 1"
229 * @param string $group The name of an arbitrary debug group.
230 * @param string $whatever A log string
231 * @param mixed $whatever... Further parameters to be replaced into the log string a la printf
233 function Dbg( $whatever )
235 global $debuggroups, $c;
237 $argc = func_num_args();
238 $dgroup = func_get_arg(0);
240 if ( ! (isset($debuggroups[$dgroup]) && $debuggroups[$dgroup]) ) return;
242 $format = func_get_arg(1);
243 if ( $argc == 2 || ($argc == 3 && func_get_arg(2) == "0" ) ) {
244 error_log( "$c->sysabbr: DBG: $dgroup: $format" );
246 else {
247 $args = array();
248 for( $i=2; $i < $argc; $i++ ) {
249 $args[] = func_get_arg($i);
251 error_log( "$c->sysabbr: DBG: $dgroup: " . vsprintf($format,$args) );
256 * Checks whether a user is allowed to do something.
258 * The check is performed to see if the user has that role.
260 * @param string $whatever The role we want to know if the user has.
261 * @return boolean Whether or not the user has the specified role.
263 function AllowedTo ( $whatever ) {
264 return ( $this->logged_in && isset($this->roles[$whatever]) && $this->roles[$whatever] );
269 * Internal function used to get the user's roles from the database.
271 function GetRoles () {
272 $this->roles = array();
273 $qry = new PgQuery( 'SELECT role_name FROM role_member m join roles r ON r.role_no = m.role_no WHERE user_no = ? ', $this->user_no );
274 if ( $qry->Exec('Session::GetRoles') && $qry->rows > 0 ) {
275 while( $role = $qry->Fetch() ) {
276 $this->roles[$role->role_name] = true;
283 * Internal function used to assign the session details to a user's new session.
284 * @param object $u The user+session object we (probably) read from the database.
286 function AssignSessionDetails( $u ) {
287 // Assign each field in the selected record to the object
288 foreach( $u AS $k => $v ) {
289 $this->{$k} = $v;
292 $qry = new PgQuery( "SET DATESTYLE TO ?;", ($this->date_format_type == 'E' ? 'European,ISO' : ($this->date_format_type == 'U' ? 'US,ISO' : 'ISO')) );
293 $qry->Exec();
295 $this->GetRoles();
296 $this->logged_in = true;
301 * Attempt to perform a login action.
303 * This will validate the user's username and password. If they are OK then a new
304 * session id will be created and the user will be cookied with it for subsequent
305 * pages. A logged in session will be created, and the $_POST array will be cleared
306 * of the username, password and submit values. submit will also be cleared from
307 * $_GET and $GLOBALS, just in case.
309 * @param string $username The user's login name, or at least what they entered it as.
310 * @param string $password The user's password, or at least what they entered it as.
311 * @param string $authenticated If true, then authentication has already happened and the password is not checked, though the user must still exist.
312 * @return boolean Whether or not the user correctly guessed a temporary password within the necessary window of opportunity.
314 function Login( $username, $password, $authenticated = false ) {
315 global $c;
316 $rc = false;
317 dbg_error_log( "Login", " Login: Attempting login for $username" );
318 if ( isset($usr) ) unset($usr); /** In case someone is running with register_globals on */
321 * TODO In here we will need to put code to call the auth plugin, in order to
322 * ensure the 'usr' table has current valid data. At this stage we are just
323 * thinking it through... like ...
326 if ( !$authenticated && isset($c->authenticate_hook) && isset($c->authenticate_hook['call']) && function_exists($c->authenticate_hook['call']) ) {
328 * The authenticate hook needs to:
329 * - Accept a username / password
330 * - Confirm the username / password are correct
331 * - Create (or update) a 'usr' record in our database
332 * - Return the 'usr' record as an object
333 * - Return === false when authentication fails
334 * It can expect that:
335 * - Configuration data will be in $c->authenticate_hook['config'], which might be an array, or whatever is needed.
337 $usr = call_user_func( $c->authenticate_hook['call'], $username, $password );
338 if ( $usr === false ) unset($usr); else $authenticated = true;
341 $sql = "SELECT * FROM usr WHERE lower(username) = ? AND active";
342 $qry = new PgQuery( $sql, strtolower($username) );
343 if ( isset($usr) || ($qry->Exec('Login',__LINE__,__FILE__) && $qry->rows == 1 && $usr = $qry->Fetch() ) ) {
344 if ( $authenticated || session_validate_password( $password, $usr->password ) || check_temporary_passwords( $password, $usr->user_no ) ) {
345 // Now get the next session ID to create one from...
346 $qry = new PgQuery( "SELECT nextval('session_session_id_seq')" );
347 if ( $qry->Exec('Login') && $qry->rows == 1 ) {
348 $seq = $qry->Fetch();
349 $session_id = $seq->nextval;
350 $session_key = md5( rand(1010101,1999999999) . microtime() ); // just some random shite
351 dbg_error_log( "Login", " Login: Valid username/password for $username ($usr->user_no)" );
353 // Set the last_used timestamp to match the previous login.
354 $qry = new PgQuery('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);
355 $qry->Exec('Session');
357 // And create a session
358 $sql = "INSERT INTO session (session_id, user_no, session_key) VALUES( ?, ?, ? )";
359 $qry = new PgQuery( $sql, $session_id, $usr->user_no, $session_key );
360 if ( $qry->Exec('Login') ) {
361 // Assign our session ID variable
362 $sid = "$session_id;$session_key";
364 // Create a cookie for the sesssion
365 setcookie('sid',$sid, 0,'/');
366 // Recognise that we have started a session now too...
367 $this->Session($sid);
368 dbg_error_log( "Login", " Login: New session $session_id started for $username ($usr->user_no)" );
369 if ( isset($_POST['remember']) && intval($_POST['remember']) > 0 ) {
370 $cookie .= md5( $usr->user_no ) . ";";
371 $cookie .= session_salted_md5($usr->user_no . $usr->username . $usr->password);
372 $GLOBALS['lsid'] = $cookie;
373 setcookie( "lsid", $cookie, time() + (86400 * 3600), "/" ); // will expire in ten or so years
375 $this->just_logged_in = true;
377 // Unset all of the submitted values, so we don't accidentally submit an unexpected form.
378 unset($_POST['username']);
379 unset($_POST['password']);
380 unset($_POST['submit']);
381 unset($_GET['submit']);
382 unset($GLOBALS['submit']);
384 if ( function_exists('local_session_sql') ) {
385 $sql = local_session_sql();
387 else {
388 $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
390 $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
392 $qry = new PgQuery($sql, $session_id, $session_key, $session_key);
393 if ( $qry->Exec('Session') && 1 == $qry->rows ) {
394 $this->AssignSessionDetails( $qry->Fetch() );
397 $rc = true;
398 return $rc;
400 // else ...
401 $this->cause = 'ERR: Could not create new session.';
403 else {
404 $this->cause = 'ERR: Could not increment session sequence.';
407 else {
408 $c->messages[] = i18n('Invalid username or password.');
409 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
410 $this->cause = 'WARN: Invalid password.';
411 else
412 $this->cause = 'WARN: Invalid username or password.';
415 else {
416 $c->messages[] = i18n('Invalid username or password.');
417 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
418 $this->cause = 'WARN: Invalid username.';
419 else
420 $this->cause = 'WARN: Invalid username or password.';
423 $this->Log( "Login failure: $this->cause" );
424 $this->login_failed = true;
425 $rc = false;
426 return $rc;
432 * Attempts to logs in using a long-term session ID
434 * This is all horribly insecure, but its hard not to be.
436 * @param string $lsid The user's value of the lsid cookie.
437 * @return boolean Whether or not the user's lsid cookie got them in the door.
439 function LSIDLogin( $lsid ) {
440 global $c;
441 dbg_error_log( "Login", " LSIDLogin: Attempting login for $lsid" );
443 list($md5_user_no,$validation_string) = split( ';', $lsid );
444 $qry = new PgQuery( "SELECT * FROM usr WHERE md5(user_no::text)=?;", $md5_user_no );
445 if ( $qry->Exec('Login') && $qry->rows == 1 ) {
446 $usr = $qry->Fetch();
447 list( $x, $salt, $y) = split('\*', $validation_string);
448 $my_validation = session_salted_md5($usr->user_no . $usr->username . $usr->password, $salt);
449 if ( $validation_string == $my_validation ) {
450 // Now get the next session ID to create one from...
451 $qry = new PgQuery( "SELECT nextval('session_session_id_seq')" );
452 if ( $qry->Exec('Login') && $qry->rows == 1 ) {
453 $seq = $qry->Fetch();
454 $session_id = $seq->nextval;
455 $session_key = md5( rand(1010101,1999999999) . microtime() ); // just some random shite
456 dbg_error_log( "Login", " LSIDLogin: Valid username/password for $username ($usr->user_no)" );
458 // And create a session
459 $sql = "INSERT INTO session (session_id, user_no, session_key) VALUES( ?, ?, ? )";
460 $qry = new PgQuery( $sql, $session_id, $usr->user_no, $session_key );
461 if ( $qry->Exec('Login') ) {
462 // Assign our session ID variable
463 $sid = "$session_id;$session_key";
465 // Create a cookie for the sesssion
466 setcookie('sid',$sid, 0,'/');
467 // Recognise that we have started a session now too...
468 $this->Session($sid);
469 dbg_error_log( "Login", " LSIDLogin: New session $session_id started for $this->username ($usr->user_no)" );
471 $this->just_logged_in = true;
473 // Unset all of the submitted values, so we don't accidentally submit an unexpected form.
474 unset($_POST['username']);
475 unset($_POST['password']);
476 unset($_POST['submit']);
477 unset($_GET['submit']);
478 unset($GLOBALS['submit']);
480 if ( function_exists('local_session_sql') ) {
481 $sql = local_session_sql();
483 else {
484 $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
486 $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
488 $qry = new PgQuery($sql, $session_id, $session_key, $session_key);
489 if ( $qry->Exec('Session') && 1 == $qry->rows ) {
490 $this->AssignSessionDetails( $qry->Fetch() );
493 $rc = true;
494 return $rc;
496 // else ...
497 $this->cause = 'ERR: Could not create new session.';
499 else {
500 $this->cause = 'ERR: Could not increment session sequence.';
503 else {
504 dbg_error_log( "Login", " LSIDLogin: $validation_string != $my_validation ($salt - $usr->user_no, $usr->username, $usr->password)");
505 $client_messages[] = i18n('Invalid username or password.');
506 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
507 $this->cause = 'WARN: Invalid password.';
508 else
509 $this->cause = 'WARN: Invalid username or password.';
512 else {
513 $client_messages[] = i18n('Invalid username or password.');
514 if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
515 $this->cause = 'WARN: Invalid username.';
516 else
517 $this->cause = 'WARN: Invalid username or password.';
520 dbg_error_log( "Login", " LSIDLogin: $this->cause" );
521 return false;
526 * Renders some HTML for a basic login panel
528 * @return string The HTML to display a login panel.
530 function RenderLoginPanel() {
531 $action_target = htmlspecialchars(preg_replace('/\?logout.*$/','',$_SERVER['REQUEST_URI']));
532 dbg_error_log( "Login", " RenderLoginPanel: action_target='%s'", $action_target );
533 $userprompt = translate("User Name");
534 $pwprompt = translate("Password");
535 $rememberprompt = str_replace( ' ', '&nbsp;', translate("forget me not"));
536 $gobutton = htmlspecialchars(translate("GO!"));
537 $gotitle = htmlspecialchars(translate("Enter your username and password then click here to log in."));
538 $temppwprompt = translate("If you have forgotten your password then");
539 $temppwbutton = htmlspecialchars(translate("Help! I've forgotten my password!"));
540 $temppwtitle = htmlspecialchars(translate("Enter a username, if you know it, and click here, to be e-mailed a temporary password."));
541 $html = <<<EOTEXT
542 <div id="logon">
543 <form action="$action_target" method="post">
544 <table>
545 <tr>
546 <th class="prompt">$userprompt:</th>
547 <td class="entry">
548 <input class="text" type="text" name="username" size="12" /></td>
549 </tr>
550 <tr>
551 <th class="prompt">$pwprompt:</th>
552 <td class="entry">
553 <input class="password" type="password" name="password" size="12" />
554 &nbsp;<label>$rememberprompt: <input class="checkbox" type="checkbox" name="remember" value="1" /></label>
555 </td>
556 </tr>
557 <tr>
558 <th class="prompt">&nbsp;</th>
559 <td class="entry">
560 <input type="submit" value="$gobutton" title="$gotitle" name="submit" class="submit" />
561 </td>
562 </tr>
563 </table>
565 $temppwprompt: <input type="submit" value="$temppwbutton" title="$temppwtitle" name="lostpass" class="submit" />
566 </p>
567 </form>
568 </div>
570 EOTEXT;
571 return $html;
576 * Checks that this user is logged in, and presents a login screen if they aren't.
578 * The function can optionally confirm whether they are a member of one of a list
579 * of groups, and deny access if they are not a member of any of them.
581 * @param string $groups The list of groups that the user must be a member of one of to be allowed to proceed.
582 * @return boolean Whether or not the user is logged in and is a member of one of the required groups.
584 function LoginRequired( $groups = "" ) {
585 global $c, $session;
587 if ( $this->logged_in && $groups == "" ) return;
588 if ( ! $this->logged_in ) {
589 $c->messages[] = i18n("You must log in to use this system.");
590 include_once("page-header.php");
591 if ( function_exists("local_index_not_logged_in") ) {
592 local_index_not_logged_in();
594 else {
595 $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>");
596 printf( $login_html, $c->system_name, $c->admin_email );
597 echo $this->RenderLoginPanel();
600 else {
601 $valid_groups = split(",", $groups);
602 foreach( $valid_groups AS $k => $v ) {
603 if ( $this->AllowedTo($v) ) return;
605 $c->messages[] = i18n("You are not authorised to use this function.");
606 include_once("page-header.php");
609 include("page-footer.php");
610 exit;
616 * E-mails a temporary password in response to a request from a user.
618 * This could be called from somewhere within the application that allows
619 * someone to set up a user and invite them.
621 * This function includes EMail.php to actually send the password.
623 function EmailTemporaryPassword( $username, $email_address, $body_template="" ) {
624 global $c;
626 $password_sent = false;
627 $where = "";
628 if ( isset($username) && $username != "" ) {
629 $where = "WHERE active AND usr.username = ". qpg($username );
631 else if ( isset($email_address) && $email_address != "" ) {
632 $where = "WHERE active AND usr.email = ". qpg($email_address );
635 if ( $where != "" ) {
636 $tmp_passwd = "";
637 for ( $i=0; $i < 8; $i++ ) {
638 $tmp_passwd .= substr( "ABCDEFGHIJKLMNOPQRSTUVWXYZ+#.-=*%@0123456789abcdefghijklmnopqrstuvwxyz", rand(0,69), 1);
640 $sql = "SELECT * FROM usr $where";
641 $qry = new PgQuery( $sql );
642 $qry->Exec("Session::EmailTemporaryPassword");
643 if ( $qry->rows > 0 ) {
644 $sql = "BEGIN;";
646 include_once("EMail.php");
647 $mail = new EMail( "Access to $c->system_name" );
648 $mail->SetFrom($c->admin_email );
649 $usernames = "";
650 if ( isset($c->debug_email) ) {
651 $debug_to = "This e-mail would normally be sent to:\n ";
652 $mail->AddTo( "WRMS Tester <$c->debug_email>" );
654 while ( $row = $qry->Fetch() ) {
655 $sql .= "INSERT INTO tmp_password ( user_no, password) VALUES( $row->user_no, '$tmp_passwd');";
656 if ( isset($c->debug_email) ) {
657 $debug_to .= "$row->fullname <$row->email> ";
659 else {
660 $mail->AddTo( "$row->fullname <$row->email>" );
662 $usernames .= " $row->username\n";
664 if ( $mail->To != "" ) {
665 if ( isset($c->debug_email) ) {
666 $debug_to .= "\n============================================================\n";
668 $sql .= "COMMIT;";
669 $qry = new PgQuery( $sql );
670 $qry->Exec("Session::SendTemporaryPassword");
671 if ( !isset($body_template) || $body_template == "" ) {
672 $body_template = <<<EOTEXT
673 $debug_to
674 @@debugging@@A temporary password has been requested for @@system_name@@.
676 Temporary Password: @@password@@
678 This has been applied to the following usernames:
680 @@usernames@@
681 and will be valid for 24 hours.
683 If you have any problems, please contact the system administrator.
685 EOTEXT;
687 $body = str_replace( '@@system_name@@', $c->system_name, $body_template);
688 $body = str_replace( '@@password@@', $tmp_passwd, $body);
689 $body = str_replace( '@@usernames@@', $usernames, $body);
690 $body = str_replace( '@@debugging@@', $debug_to, $body);
691 $mail->SetBody($body);
692 $mail->Send();
693 $password_sent = true;
697 return $password_sent;
702 * Sends a temporary password in response to a request from a user.
704 * This is probably only going to be called from somewhere internal. An external
705 * caller will probably just want the e-mail, without the HTML that this displays.
708 function SendTemporaryPassword( ) {
709 global $c;
711 $password_sent = $this->EmailTemporaryPassword( $_POST['username'], $_POST['email_address'] );
713 if ( ! $password_sent && ((isset($_POST['username']) && $_POST['username'] != "" )
714 || (isset($_POST['email_address']) && $_POST['email_address'] != "" )) ) {
715 // Username or EMail were non-null, but we didn't find that user.
717 $page_content = <<<EOTEXT
718 <div id="logon">
719 <h1>Unable to Reset Password</h1>
720 <p>We were unable to reset your password at this time. Please contact
721 <a href="mailto:$c->admin_email">$c->admin_email</a>
722 to arrange for an administrator to reset your password.</p>
723 <p>Thank you.</p>
724 </div>
725 EOTEXT;
728 if ( $password_sent ) {
729 $page_content = <<<EOTEXT
730 <div id="logon">
731 <h1>Temporary Password Sent</h1>
732 <p>A temporary password has been e-mailed to you. This password
733 will be valid for 24 hours and you will be required to change
734 your password after logging in.</p>
735 <p><a href="/">Click here to return to the login page.</a></p>
736 </div>
737 EOTEXT;
739 else {
740 $page_content = <<<EOTEXT
741 <div id="logon">
742 <h1>Temporary Password</h1>
743 <form action="$action_target" method="post">
744 <table>
745 <tr>
746 <th class="prompt" style="white-space: nowrap;">Enter your User Name:</th>
747 <td class="entry"><input class="text" type="text" name="username" size="12" /></td>
748 </tr>
749 <tr>
750 <th class="prompt" style="white-space: nowrap;">Or your EMail Address:</th>
751 <td class="entry"><input class="text" type="text" name="email_address" size="50" /></td>
752 </tr>
753 <tr>
754 <th class="prompt" style="white-space: nowrap;">and click on -></th>
755 <td class="entry">
756 <input class="submit" type="submit" value="Send me a temporary password" alt="Enter a username, or e-mail address, and click here." name="lostpass" />
757 </td>
758 </tr>
759 </table>
760 <p>Note: If you have multiple accounts with the same e-mail address, they will <em>all</em>
761 be assigned a new temporary password, but only the one(s) that you use that temporary password
762 on will have the existing password invalidated.</p>
763 <p>Any temporary password will only be valid for 24 hours.</p>
764 </form>
765 </div>
766 EOTEXT;
768 include_once("page-header.php");
769 echo $page_content;
770 include_once("page-footer.php");
771 exit(0);
774 function _CheckLogout() {
775 if ( isset($_GET['logout']) ) {
776 dbg_error_log( "Login", ":_CheckLogout: Logging out");
777 setcookie( 'sid', '', 0,'/');
778 unset($_COOKIE['sid']);
779 unset($GLOBALS['sid']);
780 unset($_COOKIE['lsid']); // Allow a cookied person to be un-logged-in for one page view.
781 unset($GLOBALS['lsid']);
783 if ( isset($_GET['forget']) ) setcookie( 'lsid', '', 0,'/');
787 function _CheckLogin() {
788 if ( isset($_POST['lostpass']) ) {
789 dbg_error_log( "Login", ":_CheckLogin: User '$_POST[username]' has lost the password." );
790 $this->SendTemporaryPassword();
792 else if ( isset($_POST['username']) && isset($_POST['password']) ) {
793 // Try and log in if we have a username and password
794 $this->Login( $_POST['username'], $_POST['password'] );
795 @dbg_error_log( "Login", ":_CheckLogin: User %s(%s) - %s (%d) login status is %d", $_POST['username'], $this->fullname, $this->user_no, $this->logged_in );
797 else if ( !isset($_COOKIE['sid']) && isset($_COOKIE['lsid']) && $_COOKIE['lsid'] != "" ) {
798 // Validate long-term session details
799 $this->LSIDLogin( $_COOKIE['lsid'] );
800 dbg_error_log( "Login", ":_CheckLogin: User $this->username - $this->fullname ($this->user_no) login status is $this->logged_in" );
802 else if ( !isset($_COOKIE['sid']) && isset($c->authenticate_hook['server_auth_type']) && $c->authenticate_hook['server_auth_type'] == $_SERVER['AUTH_TYPE']) {
804 * The authentication has happened in the server, and we should accept it.
805 * Perhaps this 'split' is not a good idea though. People may want to use the
806 * full ID as the username. A further option may be desirable.
808 list($username) = split('@', $_SERVER['REMOTE_USER']);
809 $this->Login($username, "", true); // Password will not be checked.
815 * Function to reformat an ISO date to something nicer and possibly more localised
816 * @param string $indate The ISO date to be formatted.
817 * @param string $type If 'timestamp' then the time will also be shown.
818 * @return string The nicely formatted date.
820 function FormattedDate( $indate, $type='date' ) {
821 $out = "";
822 if ( preg_match( '#^\s*$#', $indate ) ) {
823 // Looks like it's empty - just return empty
824 return $indate;
826 if ( preg_match( '#^\d{1,2}[/-]\d{1,2}[/-]\d{2,4}#', $indate ) ) {
827 // Looks like it's nice already - don't screw with it!
828 return $indate;
830 $yr = substr($indate,0,4);
831 $mo = substr($indate,5,2);
832 $dy = substr($indate,8,2);
833 switch ( $this->date_format_type ) {
834 case 'U':
835 $out = sprintf( "%d/%d/%d", $mo, $dy, $yr );
836 break;
837 case 'E':
838 $out = sprintf( "%d/%d/%d", $dy, $mo, $yr );
839 break;
840 default:
841 $out = sprintf( "%d-%02d-%02d", $yr, $mo, $dy );
842 break;
844 if ( $type == 'timestamp' ) {
845 $out .= substr($indate,10,6);
847 return $out;
852 * Build a hash which we can use for confirmation that we didn't get e-mailed
853 * a bogus link by someone, and that we actually got here by traversing the
854 * website.
856 * @param string $method Either 'GET' or 'POST' depending on the way we will use this.
857 * @param string $varname The name of the variable which we will confirm
858 * @return string A string we can use as either a GET or POST value (i.e. a hidden field, or a varname=hash pair.
860 function BuildConfirmationHash( $method, $varname ) {
862 * We include session_start in this because it is never passed to the client
863 * and since it includes microseconds would be very hard to predict.
865 $confirmation_hash = session_salted_md5( $session->session_start.$varname.$session->session_key, "" );
866 if ( $method == 'GET' ) {
867 $confirm = $varname .'='. urlencode($confirmation_hash);
869 else {
870 $confirm = sprintf( '<input type="hidden" name="%s" value="%s">', $varname, htmlspecialchars($confirmation_hash) );
872 return $confirm;
877 * Check a hash which we created through BuildConfirmationHash
879 * @param string $method Either 'GET' or 'POST' depending on the way we will use this.
880 * @param string $varname The name of the variable which we will confirm
881 * @return string A string we can use as either a GET or POST value (i.e. a hidden field, or a varname=hash pair.
883 function CheckConfirmationHash( $method, $varname ) {
884 if ( $method == 'GET' ) {
885 $hashwegot = $_GET[$varname];
887 else {
888 $hashwegot = $_POST[$varname];
890 if ( ereg('^\*(.+)\*.+$', $hashwegot, $regs ) ) {
891 // A nicely salted md5sum like "*<salt>*<salted_md5>"
892 $salt = $regs[1];
893 $test_against = session_salted_md5( $session->session_start.$varname.$session->session_key, $salt ) ;
895 if ( $hashwegot == $test_against ) return true;
897 return false;
904 * @global resource $session
905 * @name $session
906 * The session object is global.
909 if ( !isset($session) ) {
910 Session::_CheckLogout();
911 $session = new Session();
912 $session->_CheckLogin();