File/program/lib/loginlib.php

Description

/program/lib/loginlib.php -- functions to handle user login/logout

Visitors need to authenticate when they want to see a 'protected' area or when they want to modify the website content. This requires a user account and the visitor presenting valid credentials (username + password).

We don't want malicious scripts trying to get in with brute force. However, we need to accomodate users that make typo's while entering credentials. Also we want to allow for sending password reminders, in a safe way.

Features:

  • users are allowed N login attempts within an interval of T1 minutes
  • users can request a new password (a 'bypass') to be mailed to them. this additional password is valid for only T2 minutes
  • if a user has requested a bypass, the user is forced to change her password. the new password must differ from the old password and also from the bypass
  • if too many failures are detected in the last T1 minutes, login attempts from the corresponding IP-address are blocked for T3 minutes
N = $CFG->login_max_failures, default 10

T1 = $CFG->login_failures_interval, default 12 minutes

T2 = $CFG->login_bypass_interval, default 30 minutes

T3 = $CFG->login_blacklist_interval, default 8 minutes

Once a user is authenticated, a PHP-session is established, using our own database based session handler. The session key is stored in a cookie in the user's browser. Presenting this cookie on subsequent calls is enough to gain access. The logout routine takes care of killing both the user's cookie and the session in the database.

There are several different login procedures.

  1. Normal login The user enters a valid username and password and is subsequently logged in.
2. Change password The user enters a valid username and password and also a valid new password (twice). A salted hash of the new password is recorded in the database and the user is logged in.

3. Forgotten password, phase 1: sending a laissez-passer The user presents a valid combination of username and email address. Subsequently a one-time logon-code (dubbed 'laissez-passer') is sent to the user's email address. This code is valid for at most T2 minutes. This code can be used, exactly once, to send a temporary password via email.

4. Forgotten password, phase 2: sending a temporary password The user clicks the link received in phase 1 and a temporary password (dubbed 'bypass') is sent to the user. This temporary password is valid for another T2 minutes.

5. Message box This is a pseudo-procedure. A simple 'message box' type of screen is displayed but no real interaction is anticipated via this screen. This is used to tell the user that things didnt work out (too many failures) or to check their mail for further instructions (e.g. when a laissez passer was sent). Whenever the user acknowledges this screen by clicking the button, she usually is directed to $WAS_SCRIPT_NAME.

6. Blacklist This is also a pseudo-procedure. The corresponding number is used to identify blacklisted IP-addresses in the database.

Note that when the user logs in after a temporary password has been sent, the normal login procedure is immediately followed by a (forced) 'change password' procedure. This makes sure that a temporary password will be changed immediately after the user logs in.

Note that each of the procedures can be entered 'manually', i.e. by opening index.php?login=X the user starts procedure X. This allows for the user to change her password whenever she feels this is necessary, without going through the trouble of the 'forgotten password'-procedure which eventually ends with the user changing her password too.

  • author: Peter Fokker <peter@berestijn.nl>
  • version: $Id: loginlib.php,v 1.4 2011-05-04 07:32:46 pfokker Exp $
  • copyright: Copyright (C) 2008-2011 Ingenieursbureau PSD/Peter Fokker
  • todo: should we normalize the remote_addr everywhere? We now rely on the remote_addr being equal to some stored value (in the database) but with an IPv6 address there are several possibilities to have different representations of the same address (e.g. '::dead:beef' is equivalent to ::0:dead:beef' or even '::DeAd:BeeF' or '0000:0000:0000:0000:0000:0000:DEAD:BEEF'. This problem also exists with IPv4: '127.0.0.1' is equivalent to '127.000.000.001'. *sigh*
  • todo: should we suppress the username in the laissez-passer routine? We _do_ leak the the username in an insecure email message. This does require making the laissez-passer code unique in the database (currently only username+code has to be unique and that's easy because the username itself is unique).
  • license: GNU AGPLv3+Additional Terms
Constants
BY_EMAIL = 2 (line 159)

this selects authentication via username+email in authenticate_user()

BY_LAISSEZ_PASSER = 3 (line 162)

this selects authentication via username+laissez_passer in authenticate_user()

BY_PASSWORD = 1 (line 156)

this selects authentication via username+password in authenticate_user()

LOGIN_DEBUG = 0 (line 117)

useful when debugging routines in this file: 0=production, 1=debugging

LOGIN_FAILURE_DELAY_SECONDS = 3 (line 141)

this is the number of seconds to delay responding after a login action fails (slow 'm down..)

LOGIN_PROCEDURE_BLACKLIST = 6 (line 138)

this is a pseudo procedure, used to record blacklisted IP-addresses

LOGIN_PROCEDURE_CHANGE_PASSWORD = 2 (line 126)

this is the procedure to change the user's password

LOGIN_PROCEDURE_MESSAGE_BOX = 5 (line 135)

this is a pseudo procedure, used to deliver some message to the user

LOGIN_PROCEDURE_NORMAL = 1 (line 123)

this is the usual procedure for logging in

LOGIN_PROCEDURE_SEND_BYPASS = 4 (line 132)

this is phase 2 of the 'forgot password' procedure

LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER = 3 (line 129)

this is phase 1 of the 'forgot password' procedure

LOGIN_PROCEDURE_SHOWLOGIN = 0 (line 120)

this only shows the login dialog

MAXIMUM_LINE_LENGTH = 50 (line 165)

this defines the maximum line length in messages and instructions

MINIMUM_PASSWORD_DIGITS = 1 (line 153)

this is the hardcoded minimal number of digits in a new password

MINIMUM_PASSWORD_LENGTH = 6 (line 144)

this hardcoded minimal length is enforced whenever a user wants to change her password

MINIMUM_PASSWORD_LOWERCASE = 1 (line 147)

this is the hardcoded minimal number of lower case characters in a new password

MINIMUM_PASSWORD_UPPERCASE = 1 (line 150)

this is the hardcoded minimal number of upper case characters in a new password

Functions
acceptable_new_password (line 1278)

check the new passwords satisfy password requirements

Users should provide the same password twice, to prevent typo's, so both passwords should be equal. Also, the following requirements should be satisfied:

  • the minimum password length should be MINIMUM_PASSWORD_LENGTH (default 6)
  • the new password should contain at least MINIMUM_PASSWORD_LOWERCASE lowercase letter a-z (default 1)
  • the new password should contain at least MINIMUM_PASSWORD_UPPERCASE upper case letter A-Z (default 1)
  • the new password should contain at least MINIMUM_PASSWORD_DIGITS digit 0-9 (default 1)
  • the new password should not be the same as the previous password
  • the new password should not be the same as the bypass password (if any was issued)
Note that the bypass-generator also should satisfy these rules. This could lead to the thought of accepting the bypass-password as the permanent one. However, since this temporary password was sent to the user in a plain-text email message, we should consider this a 'bad' password.

The minimum password length and other minimum values are not configurable (via $CFG) because that would make it too easy (too tempting) to give in and use weak passwords (too short, only lowercase, etc.) However, if your really MUST, you could change the MINIMUM_PASSWORD_* constants defined above.

Note that the check agains existing (temporary and regular) passwords is not performed if the corresponding parameters are empty. If they are empty, this routine only performs the first 4 checks in the list above.

  • return: TRUE if new password is acceptable, FALSE otherwise
bool acceptable_new_password (string $new_password1, string $new_password2, [string $salt = ''], [string $password_hash = ''], [string $bypass_hash = ''])
  • string $new_password1: new password
  • string $new_password2: new password again, to prevent typo's
  • string $salt: (optional) the salt that was used to hash the old password and the bypass
  • string $password_hash: (optional) the hashed existing password
  • string $bypass_hash: (optional) the hashed bypass password
authenticate_user (line 1174)

check the user's credentials in one of three ways

This authenticates the user's credentials. There are some variants:

  • by password: the user's password should match
  • by email: the user's email should match
  • by laissez passer: the one-time authentication code should match
Strategy: we first read the active record for user $username in core. If there is none, the user does not exist or is inactive => return FALSE.

After that we check the validity of the token:

  • a password is checked via the password hash or, if that fails, via the bypass hash. In the latter case, the bypass should not yet be expired (a bypass and a laissez_passer are valid until the 'bypass_expiry' time).
  • an email address is checked caseINsensitive and without leading/trailing spaces
  • a laissez_passer is check much the same way as the bypass password, be it that the code is stored 'as-is' rather than as a hash. The comparison is caseINsensitive.
If the credentials are considered valid, an array with the user record is returned, otherwise FALSE is returned.

Because there are actually several checks to be done, we decided not to use SQL like: SELECT * FROM users WHERE username=$username AND password=$password, not the least because we need to have the salt in our hands before we can successfully compare password hashes.

Note: The 'special cases' (checking email, checking laissez_passer, checking bypass) all have their token stripped from leading and trailing spaces. We don't want to further confuse the user by not accepting a spurious space that was entered in the heat of the moment when the user has 'lost' her password. Therefore we also always trim the username. Rationale: usernames and also the generated passwords etc. never have leading/trailing spaces. However, one cannot be sure that a user has not entered a real password with leading/trailing space, so we do NOT trim the $token in the first attempt in the case 'BY_PASSWORD' below.

  • return: FALSE if invalid credentials, array with user record otherwise
bool|array authenticate_user (int $by_what_token, string $username, string $token)
  • int $by_what_token: which authentication token to use
  • string $username: username the user entered in the dialog
  • string $token: the token is either password, email or laissez_passer entered by the user
login_change_password (line 1227)

update the users database with a new (randomly salted) password and reset bypass mode to normal

This updates the user record for user with user_id and stores the new password. The new password and a new random salt are hashed together and the result is stored, together with the new salt, overwriting the old salt and the old password hash. The bypass mode is reset to normal and the bypass hash is reset. Return TRUE on success.

  • return: FALSE on failure, TRUE otherwise
bool login_change_password (int $user_id, string $new_password)
  • int $user_id: identify the user record by user_id
  • string $new_password: the new password in plain text
login_dialog_close (line 846)

close the login dialog/table and maybe an opened HTML-form

string login_dialog_close ([string $action = ''], [string $m = ''])
  • string $action: (optional) if not empty the currently open HTML-form is closed
  • string $m: (optional) margin to add for code readability
login_dialog_home_forgot_password (line 942)

add a row with links to home page and forgot password dialog to the login dialog/table

This constructs a link to the home page in the left hand dialog/table column and optionally a link to the start of the forgot password dialog. The latter is only displayed if $forgot is not empty.

  • return: constructed HTML
string login_dialog_home_forgot_password (string $forgot, [string $m = ''])
  • string $forgot: anchor text to display with link to forgot password dialog
  • string $m: (optional) margin to add for code readability
login_dialog_instruction (line 858)

add a row to the table/dialog with wordwrap()'ed instruction for the user

  • return: constructed HTML
string login_dialog_instruction (string $instruction, [string $m = ''])
  • string $instruction: instructive message to show to user
  • string $m: (optional) margin to add for code readability
login_dialog_open (line 802)

construct the start of the login dialog, opening the form and the secondary table

This optionally opens an HTML-form (which is optionally closed in companion routine login_dialog_close()) and subsequently starts a table with two columns. The first row of the table shows the $title, the second row may show a wordwrap()'ed feedback message for the user in a different background colour.

  • return: constructed HTML
  • see: login_dialog_close()
  • todo: should we add another 'powered by' link to '/program/about.html'?
string login_dialog_open (string $title, [string $action = ''], [string $message = ''], [string $m = ''])
  • string $title: text to show in the dialog title bar
  • string $action: (optional) if not empty a HTML-form pointing to $action is opened
  • string $message: (optional) feedback message to show to user
  • string $m: (optional) margin to add for code readability
login_dialog_password_input (line 899)

add a row with a password input field to the login dialog/table

This generates HTML for another table row. Special feature: try to suppress autocomplete via an extra parameter. See

  • return: constructed HTML
string login_dialog_password_input (string $prompt, string $name, [int $tabindex = ''], [string $m = ''])
  • string $prompt: the text to show in the 1st column of the table row
  • string $name: the name of the input field
  • int $tabindex: (optional) determines in which order fields are accessed in the dialog
  • string $m: (optional) margin to add for code readability
login_dialog_submit_input (line 919)

add a row with a submit button to the login dialog/table

  • return: constructed HTML
string login_dialog_submit_input (string $buttontext, string $name, [int $tabindex = ''], [string $m = ''])
  • string $buttontext: the text to show in the button
  • string $name: the name of the button
  • int $tabindex: (optional) determines in which order fields are accessed in the dialog
  • string $m: (optional) margin to add for code readability
login_dialog_text_input (line 875)

add a row with an ordinary input field to the login dialog/table

  • return: constructed HTML
string login_dialog_text_input (string $prompt, string $name, [string $value = ''], [int $tabindex = ''], [string $m = ''])
  • string $prompt: the text to show in the 1st column of the table row
  • string $name: the name of the input field
  • string $value: (optional) value to preload the field value (default empty string)
  • int $tabindex: (optional) determines in which order fields are accessed in the dialog
  • string $m: (optional) margin to add for code readability
login_failure_blacklist_address (line 1432)

add remote_addr to the blacklist for specified interval (in seconds)

  • return: FALSE on error, the id of the inserted record on success
bool|int login_failure_blacklist_address (string $remote_addr, int $delay_in_seconds, [string $username = ''])
  • string $remote_addr: the remote IP-address is the origin of the failure
  • int $delay_in_seconds: the number of seconds to put this address on the blacklist
  • string $username: extra information, could be useful for troubleshooting afterwards
login_failure_delay (line 1466)

delay execution of this script for a few seconds and blacklist the remote_addr during the delay

This immediately blacklists the remote address for LOGIN_FAILURE_DELAY_SECONDS seconds. Once that is done, the execution is delayed for that same period of time. After the delay, the temporary blacklisting is removed from the table. The whole purpose of this rapid succession of an INSERT and a DELETE is to prevent brute force attack scripts that do not wait for an answer and/or use multiple connections. This routine defeats that trick, because nothing can be done when an IP-address is blacklisted.

  • return: FALSE on failure, or 1 on success
  • uses: $CFG
bool|int login_failure_delay (string $remote_addr)
  • string $remote_addr: the remote IP-address that is the origin of the failure
login_failure_increment (line 1487)

add 1 point to score for a particular IP-address and failed procedure, return the new score

This records a login failure in a table and returns the the number of failures for the specified procedure in the past T1 minutes.

  • return: the current score
int login_failure_increment (string $remote_addr, int $procedure, [string $username = ''])
  • string $remote_addr: the remote IP-address that is the origin of the failure
  • int $procedure: indicates in which procedure the user failed
  • string $username: extra information, could be useful for troubleshooting afterwards
login_failure_reset (line 1421)

deactivate all login failures/blacklisting scores for remote_addr

This resets all the scores for all failed login attempts and blacklistings for the specified IP-addres. The records in the login_failures table are deactivated by deleting the records for this remote_addr.

Note that the failed logins and the blacklistings are recorede in the log_messages table via logger(). Therefore we can automatically keep this table 'login_failures' clean without cron jobs.

This routine resets _all_ scores, including any blacklisting that might still be active, i.e. which has a datim in the future.

  • return: FALSE on error, the number of deactivated failures on success
bool login_failure_reset (string $remote_addr)
  • string $remote_addr: the remote IP-address is the origin of the failure
login_is_blacklisted (line 1390)

find out if a remote address is blacklisted at this time

This routine checks if this remote address is blacklisted in the login_failures table with a datim that lies in the future. If this is the case, the address is indeed blacklisted and TRUE is returned. Note that we sum the points much the same way as in login_failure_increment rather than counting 'blacklist-records'.

  • return: FALSE if the IP-address is not blacklisted, TRUE otherwise
bool login_is_blacklisted (string $remote_addr)
  • string $remote_addr: the remote IP-address to be checked
login_page_close (line 769)

construct the end of the simple HTML-page, closing the full size table

string login_page_close ([string $alert_message = ''])
  • string $alert_message: (optional) message to show via a javascript alert()
login_page_open (line 705)

construct the start of a simple HTML-page and open a full size table

This routine starts with a plain HTML-page, with a title and possibly a single line of Javascript to place the cursor in a particular input field (defined later in the page). After that, a main table is opened and within that 1x1 table the table cell is opened. login_page_close() closes that cell.

string login_page_open (string $title, [string $focus = ''])
  • string $title: the title of the HTML-page
  • string $focus: (optional) the name of the field to focus on (via Javascript)
login_send_bypass (line 1052)

send a new (temporary) password to the user via email

This generates a new temporary password for the user, stores it in the user record and sends an email message to the user with the temporary password (in plain text) and further instructions.

Note that the password is valid only for a limited time; sending a password in plain text appears to be an acceptable risk. Note that the limited time is increased with 10% in order to give the user a reasonable margin to enter the correct password.

Also note that the existing salt is used to salt the temporary password; this makes it easier to check for validity of both the regular password and the temporary password lateron.

A log message recording the event is added via logger().

  • return: FALSE on failure, TRUE otherwise
  • uses: logger()
  • uses: $CFG
bool login_send_bypass (array $user)
  • array $user: an associative array with the user record
login_send_confirmation (line 1106)

send email to user confirming password change

This sends an email to the user's email addres confirming that the user's password was changed. Note that the new password is _NOT_ sent to the user.

  • return: FALSE on failure, TRUE otherwise
  • uses: $CFG
bool login_send_confirmation (array $user)
  • array $user: an associative array with the user record
login_send_laissez_passer (line 986)

send a special one-time login code to the user via email

This generates a temporary code with which the user can request a new temporary password. This code can be used only once. Note that this code is valid for only a limited time. This code simply overwrites the bypass password (the temporary password) in the user record. This means that if a phase 2 is pending, a new phase 1 will replace the old phase 2.

The temporary code consists of digits and uppercase characters. However, it is longer (20 characters) than the minimum password length of 6, so a brute force on such a code will likely not succeed (36^20 is much more than the usual 62^6).

This routine also brings the user's record into 'bypass mode'. This mode is reset to 'normal' after the user has successfully changed her password.

A log message recording the event is added via logger().

  • return: FALSE on failure otherwise TRUE
  • uses: logger()
  • uses: $CFG
bool login_send_laissez_passer (array $user)
  • array $user: the user record from database
login_stylesheet (line 730)

a simple in-line style sheet conveniently grouped in a single routine

  • return: ready-to-use CSS-code including style tags
  • todo: this routine needs some cleaning up
string login_stylesheet ()
password_hash (line 1332)

calculate a hash from a salt and a password

This routine constructs a hash of the combination of salt and password. By default the md5() function is used to calculate a 32-character long string of hexadcimal digits. If the parameter $algorithm is 1 then the sha1() function is used and a 40-character long string of hexadecimal digits is returned.

Note that we do not use the crypt() function because that could introduce a portability issue. If a website is migrated to another machine, the used crypt algoritm might no longer be available, and that would effectively lock out all users. Both md5() and sha1() are standard PHP-functions (since 4.3.x) and should be portable, which makes any installed table of users portable too.

  • return: a hexadecimal representation of the hash of the combination of salt and password
  • usedby: password_hash_check()
string password_hash (string $salt, string $password, [int $algorithm = 0])
  • string $salt
  • string $password
  • int $algorithm: (optional) algorithm to use: 0=md5, 1=sha1
password_hash_check (line 1359)

check equivalency of salt+password against hash

This verifies whether the hash of $salt and $password is the same as $hash. Note that the two hashes are compared in a caseINsensitive way. Usually these hashes are using lowercase hexadecimal digits but a caseINsensitive compare makes A,...,F equivalent to a,...,f.

If the length of the presented $hash is 40 characters, it is assumed that the hash algorithm to use is sha1, otherwise the default algorithm (md5) is used.

  • return: TRUE if salt+password are equivalent to hash, FALSE otherwise
  • uses: password_hash()
bool password_hash_check (string $salt, string $password, string $hash)
  • string $salt: salt
  • string $password: password to check
  • string $hash: hash to check against
password_salt (line 1373)

generate a quasi random string to salt the password hash

this generates a quasi-randomg string of digits and letters to be used as a salt when calculating a password hash.

  • return: quasi-random string
string password_salt ([int $length = 12])
  • int $length: the number of characters in the generated string
show_login (line 579)

show complete login dialog and exit

There are different variations of this dialog.

  1. LOGIN_PROCEDURE_NORMAL Plain login
       (message)
       Username: _____
       Password: _____
       [OK]
        
This screen is used for plain user authentication. As a rule the user uses the correct primary password to authenticate. However, it is also possible to enter the 'bypass' password instead. If the authentication fails, that fact is recorded. If the number of failures exceeds threshold N, the user is shown screen #3 LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER. If the number is still below N1 screen #1 is shown again.

The link <forgotten password?> takes the user directly to screen #3 LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER.

2. LOGIN_PROCEDURE_CHANGE_PASSWORD - Login/change password

   (message)
   Username: _____
   Old password: _____
   New password1: _____
   New password2: _____
   [OK]
This screen is used to change the user's password. If both new passwords are different, the user is redirected to the same screen #3 until she gets it right. Otherwise, if the old password is either the valid original password OR the bypass password, the password is changed and the mode is reset to 'normal'. The one-time codes and the bypass password are reset. Also, as a result, the user is logged in. If the user failed to enter the proper old password more than N1 times, the mode is also reset to normal (invalidating the laissez-passer and the bypass password) and the user is dropped at a screen #4 basically telling her to contact the webmaster. In this process the user is also logged out if necessary.

3. LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER Request bypass

   (message)
   Username: _____
   Email: _____
   [OK]
This screen is used to help the user reset her password. It is displayed automatically after N1 failed login attempts. This screen can also be reached via the <forgot password?> link in screen #1.

If the user presents an invalid combination of username and email address, this failure is also recorded. If the number of failures has reached the threshold N2, the user is taken to a screen #4 that basically tells the user ask the webmaster for assistance and that's that.

If the user presents a valid combination of username and email address, an email with a message like 'click the link below for a new password' is sent to the email address. After that mail is sent a screen #4 is displayed, basically telling the user to await further instructions that were sent via mail.

Note that resetting the password is a two-step process. First the user is sent a one-time code laissez-passer embedded in a link. Clicking the link before it expires (after T minutes) yields a second emai message containing a bypass password that can be used to login and subsequently change the primary password. After that both the laissez-passer and the bypass password are invalidated.

4. LOGIN_PROCEDURE_SEND_BYPASS - Send a temporary password

Phase 2 of the forgot password procedure.

5. LOGIN_PROCEDURE_MESSAGE_BOX Alert

   (message)

This screen is user to communicate various messages to the user, e.g. 'check your mail for instructions', 'contact webmaster', etc.

  • return: this routine never returns but it does send a page to the user
void show_login ([int $screen = 1], [string $message = ''], [string $username = ''])
  • int $screen: the screen variant to show, could be 1,...,5
  • string $message: the message to show just above the first field, used for feedback to user
  • string $username: the default username to show in the dialog
was_login (line 267)

execute the selected login procedure

The login process is controlled via the parameter 'login' provided by the user via 'index.php?login=N or via the 'action' property in a HTML-form. These numbers correspond to the LOGIN_PROCEDURE_* constants defined near the top of this file. Here's a reminder:

  1. LOGIN_PROCEDURE_NORMAL this is the usual procedure for logging in
  2. LOGIN_PROCEDURE_CHANGE_PASSWORD this is the procedure to change the user's password
  3. LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER this is phase 1 of the 'forgot password' procedure
  4. LOGIN_PROCEDURE_SEND_BYPASS this is phase 2 of the 'forgot password' procedure
Note that this routine only returns to the caller after either a succesful regular login (i.e. after completing LOGIN_PROCEDURE_NORMAL). All the other variants and error conditions yield another screen and an immediate exit and hence no return to caller. If this routine returns, it returns the user_id of the authenticated user (the primary key into the users table). It is up to the caller to retrieve additional information about this user; any information read from the database during login is discarded. This prevents password hashes still lying around.

Note that a successful login has the side effect of garbage collection: whenever we experience a successful login any obsolete sessions are removed. This makes sure that locked records eventually will be unlocked, once the corresponding session no longer exists. The garbage collection routine is also called from the PHP session handler every once in a while, but here we make 100% sure that garbage is collected at least at every login. (Note: obsolete sessions should not be a problem for visitors that are not logged in, because you have to be logged in to be able to lock a record.)

void|int was_login ([int $procedure = LOGIN_PROCEDURE_SHOWLOGIN], [string $message = ''])
  • int $procedure: the login procedure to execute
  • string $message: the message to display when showing the login dialog
was_logout (line 187)

end a session (logout the user) and maybe redirect

This routine ends the current session if it exists (as indicated by the cookie presented by the user's browser). An empty value is sent to the browser (effectively deleting the cookie) and also the session is ended. The routine ends either with showing a generic login dialog OR a redirection to a user-defined page.

Note that as a rule this routine does NOT return but instead calls exit(). However, there are cases where this routine DOES return, notably when no session appears to be established (no cookie submitted by the browser or a non-existing/expired session). If the routine does return, the status is equivalent to a logged out user; no session exists so the user simply should not be logged in.

void was_logout ()

Documentation generated on Wed, 11 May 2011 23:45:21 +0200 by phpDocumentor 1.4.0