/** logout the user and exit * * This logs out the user (ie kills the session) * If there is an error (ie, there was no session in the first place) * indicated by was_logout() returning, we * unconditionally show a login dialog and exit. * So, this routine never returns. * * @return void this function never returns * @uses $CFG * @uses loginlib.php */ function admin_logout_and_exit() { global $CFG; /** loginlib.php contains both login- and logout-routines */ require_once $CFG->progdir . '/lib/loginlib.php'; was_logout(); was_login(LOGIN_PROCEDURE_SHOWLOGIN); exit; }
/** main program for visitors * * this routine is called from /index.php. It is the main program for visitors. * * @return void page sent to the browser * @todo cleanup login/logout-code */ function main_index() { global $USER; global $CFG; global $LANGUAGE; /** initialise the program, setup database, read configuration, etc. */ require_once $CFG->progdir . '/init.php'; initialise(); was_version_check(); // this never returns if versions don't match // TODO: cleanup like in main_admin() // handle login/logout/continuation so we quickly find out which user is calling if (isset($_GET['logout'])) { /** loginlib.php contains both login- and logout-routines */ require_once $CFG->progdir . '/lib/loginlib.php'; was_logout(); // may or may not return here } elseif (isset($_GET['login'])) { /** loginlib.php contains both login- and logout-routines */ require_once $CFG->progdir . '/lib/loginlib.php'; was_login(magic_unquote($_GET['login'])); // may or may not return here } elseif (isset($_COOKIE[$CFG->session_name])) { /** dbsessionlib.php contains our own database based session handler */ require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); if (dbsession_exists(magic_unquote($_COOKIE[$CFG->session_name]))) { session_start(); } } // At this point we either have a valid session with a logged-in user // (indicated via existence of $_SESSION) or we are dealing with an anonymous // visitor with non-existing $_SESSION. Keep track of the number of calls // this user makes (may be logged lateron on logout). if (isset($_SESSION)) { if (!isset($_SESSION['session_counter'])) { // first time after login, record start time of session $_SESSION['session_counter'] = 1; $_SESSION['session_start'] = strftime("%Y-%m-%d %T"); } else { $_SESSION['session_counter']++; } } // Now is the time to create a USER object, even when the visitor is just a passerby // because we can then determine easily if a visitor is allowed certain things, e.g. // view a protected area or something /** useraccount.class.php is used to define the USER object */ require_once $CFG->progdir . '/lib/useraccount.class.php'; if (isset($_SESSION) && isset($_SESSION['user_id'])) { $USER = new Useraccount($_SESSION['user_id']); $USER->is_logged_in = TRUE; $_SESSION['language_key'] = $LANGUAGE->get_current_language(); // remember language set via _GET or otherwise } else { $USER = new Useraccount(); $USER->is_logged_in = FALSE; } // Check for the special preview-mode // This allows a webmaster to preview a page in the correct environment (theme) // even when the page is under embargo. Note that the node_id and area_id are // retrieved from the session; the user only has a cryptic preview-code. // See pagemanagerlib.php for more information (function task_page_preview()). $in_preview_mode = FALSE; if ($USER->is_logged_in) { $preview_code_from_url = get_parameter_string('preview'); if (!is_null($preview_code_from_url) && isset($_SESSION['preview_salt']) && isset($_SESSION['preview_node'])) { $hash = md5($_SESSION['preview_salt'] . $_SESSION['preview_node']); if ($hash === $preview_code_from_url) { $node_id = intval($_SESSION['preview_node']); $area_id = intval($_SESSION['preview_area']); $area = db_select_single_record('areas', '*', array('area_id' => $area_id)); if ($area === FALSE) { logger("Fatal error 070: cannot preview node '{$node_id}' in area '{$area_id}'"); error_exit('070'); } else { $tree = tree_build($area_id); $in_preview_mode = TRUE; } } } } if ($in_preview_mode == FALSE) { $requested_area = get_requested_area(); $requested_node = get_requested_node(); $req_area_str = is_null($requested_area) ? "NULL" : strval($requested_area); $req_node_str = is_null($requested_node) ? "NULL" : strval($requested_node); if (($area = calculate_area($requested_area, $requested_node)) === FALSE) { logger("Fatal error 080: no valid area (request: area='{$req_area_str}', node='{$req_node_str}')"); error_exit('080'); // no such area } $area_id = intval($area['area_id']); // If $USER has no permission to view area $area_id, we simply bail out. // Rationale: if the user is genuine, she knows about logging in first. // If the user is NOT logged in and tries to view a protected area, I'd consider // it malicious, and in that case I won't even confirm the existence of // the requested area. (If a cracker simply tries areas 0,1,.. and sometimes is greeted // with 'please enter credentials' and sometimes with 'area does not exist', this // provides information to the cracker. I don't want that). Note that the error code // is the same as the one for non-existing area. In other words: for an unauthorised // visitor an existing private area is just as non-existent as a non-existing public area. if (db_bool_is(TRUE, $area['is_private']) && !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $area_id)) { logger(sprintf("Fatal error 080: no view permissions for area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str)); error_exit('080'); // no such area } // still here? // then we've got a valid $area_id and corresponding $area record. // now we need to figure out which $node_id to use $tree = tree_build($area_id); if (($node_id = calculate_node_id($tree, $area_id, $requested_node)) === FALSE) { logger(sprintf("Fatal error 080: no valid node within area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str)); error_exit('080'); // no such area } } // At this point we have the following in our hands // - a valid $area_id // - a valid $node_id // - the complete tree from area $area_id in $tree // - the area record from database in $area // - the node record from database in $tree[$node_id]['record'] // - a flag that signals preview mode in $in_preview_mode // We are on our way to generate a full page with content and all, // but otoh we MIGHT be in the middle of a redirect, so we may have to // leave without showing anything at all... if (!empty($tree[$node_id]['record']['link_href'])) { update_statistics($node_id); if (isset($_SESSION)) { session_write_close(); } redirect_and_exit(htmlspecialchars($tree[$node_id]['record']['link_href'])); // exit; redirect_and_exit() never returns } /** themelib contains the theme factory */ require_once $CFG->progdir . '/lib/themelib.php'; // And now we know about the $area, we can carry on determining which $theme to use. // $theme = theme_factory($area['theme_id'], $area_id, $node_id); if ($theme === FALSE) { logger("Fatal error 090: cannot setup theme '{$area['theme_id']}' in area '{$area_id}'"); error_exit('090'); } // Tell the theme about the preview mode $theme->set_preview_mode($in_preview_mode); // Now all we need to do is let the module connected to node $node_id generate output $module_id = $tree[$node_id]['record']['module_id']; module_view($theme, $area_id, $node_id, $module_id); // Remember this visitor update_statistics($node_id); // Finally, send output to user $theme->send_output(); if (isset($_SESSION)) { session_write_close(); } // done! exit; }
/** 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.) * * @param int $procedure the login procedure to execute * @param string $message the message to display when showing the login dialog * @return void|int no return on error, otherwise the user_id of the authenticated user * @uses $CFG * @uses dbsession_setup() * @uses dbsession_garbage_collection() */ function was_login($procedure = LOGIN_PROCEDURE_SHOWLOGIN, $message = '') { global $CFG; // get rid of the cookie (if we received one) and the corresponding session; // the user's browser should NOT present us with a cookie during the login procedures. if (isset($_COOKIE[$CFG->session_name])) { was_logout(); exit; } // If this IP-address is currently blacklisted, tell the visitor access is denied if (login_is_blacklisted($_SERVER['REMOTE_ADDR'])) { show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('access_denied', 'loginlib')); exit; } switch (intval($procedure)) { case LOGIN_PROCEDURE_NORMAL: if (isset($_POST['login_username']) && isset($_POST['login_password'])) { $username = magic_unquote($_POST['login_username']); $password = magic_unquote($_POST['login_password']); $user = authenticate_user(BY_PASSWORD, $username, $password); if ($user !== FALSE) { login_failure_reset($_SERVER['REMOTE_ADDR']); if (db_bool_is(FALSE, $user['bypass_mode'])) { // valid credentials and not in a bypass mode: start session and return user_id require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); $session_key = dbsession_create($user['user_id'], $_SERVER['REMOTE_ADDR']); session_id($session_key); session_start(); $_SESSION['session_id'] = dbsession_get_session_id($session_key); $user_id = intval($user['user_id']); $_SESSION['user_id'] = $user_id; $_SESSION['redirect'] = $user['redirect']; $_SESSION['language_key'] = $user['language_key']; $_SESSION['remote_addr'] = $_SERVER['REMOTE_ADDR']; $_SESSION['salt'] = $CFG->salt; // allow for extra check on rogue sessions $_SESSION['username'] = $username; logger('login: \'' . $username . '\' (' . $user_id . '): success', WLOG_INFO, $user_id); // now that we logged on successfully, make sure that obsolete sessions // will not bother us (or other logged in users). Note the 900 seconds minimum duration; // $time_out = max(900,intval(ini_get('session.gc_maxlifetime'))); global $CFG; $time_out = max(900, intval($CFG->session_expiry)); dbsession_garbage_collection($time_out); return $user_id; // SUCCESS! User is logged in, tell caller! } else { show_login(LOGIN_PROCEDURE_CHANGE_PASSWORD, t('must_change_password', 'loginlib')); exit; } } // Invalid credentials; pretend we're busy (slow user down), increment failure count... $failure_count = login_failure_increment($_SERVER['REMOTE_ADDR'], LOGIN_PROCEDURE_NORMAL, $username); login_failure_delay($_SERVER['REMOTE_ADDR']); if ($failure_count < intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_NORMAL, t('invalid_credentials_please_retry', 'loginlib')); } elseif ($failure_count == intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER, t('do_you_want_to_try_forgot_password_procedure', 'loginlib')); } else { login_failure_blacklist_address($_SERVER['REMOTE_ADDR'], 60 * intval($CFG->login_blacklist_interval), $username); show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('contact_webmaster_for_new_password', 'loginlib')); } } else { show_login(LOGIN_PROCEDURE_NORMAL); } exit; break; case LOGIN_PROCEDURE_CHANGE_PASSWORD: if (isset($_POST['login_username']) && isset($_POST['login_password']) && isset($_POST['login_new_password1']) && isset($_POST['login_new_password2'])) { $username = magic_unquote($_POST['login_username']); $password = magic_unquote($_POST['login_password']); $new_password1 = magic_unquote($_POST['login_new_password1']); $new_password2 = magic_unquote($_POST['login_new_password2']); $user = authenticate_user(BY_PASSWORD, $username, $password); // // step 1 - perform some checks on the proposed new passwords // if ($user !== FALSE) { // user authenticated: we can now also check re-use of existing passwords $salt = $user['salt']; $password_hash = $user['password_hash']; $bypass_hash = $user['bypass_hash']; } else { // user not authenticated so we cannot check for re-use of existing passwords $salt = ''; $password_hash = ''; $bypass_hash = ''; } if (!acceptable_new_password($new_password1, $new_password2, $salt, $password_hash, $bypass_hash)) { show_login(LOGIN_PROCEDURE_CHANGE_PASSWORD, t('invalid_new_passwords', 'loginlib', array('{MIN_LENGTH}' => MINIMUM_PASSWORD_LENGTH, '{MIN_LOWER}' => MINIMUM_PASSWORD_LOWERCASE, '{MIN_UPPER}' => MINIMUM_PASSWORD_UPPERCASE, '{MIN_DIGIT}' => MINIMUM_PASSWORD_DIGITS))); exit; } // // step 2 - if authenticated, actually change password and reset failure counters/blacklists // if ($user !== FALSE) { // allow the user in: // - start new session, // - immediately write/close it, // - send user an email about success with changing password, // - and finally leave the user with message box on screen // login_change_password($user['user_id'], $new_password1); login_failure_reset($_SERVER['REMOTE_ADDR']); require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); $session_key = dbsession_create($user['user_id'], $_SERVER['REMOTE_ADDR']); session_id($session_key); session_start(); $_SESSION['session_id'] = dbsession_get_session_id($session_key); $user_id = intval($user['user_id']); $_SESSION['user_id'] = $user_id; $_SESSION['redirect'] = $user['redirect']; $_SESSION['language_key'] = $user['language_key']; $_SESSION['remote_addr'] = $_SERVER['REMOTE_ADDR']; $_SESSION['salt'] = $CFG->salt; // allow for extra check on rogue sessions $_SESSION['username'] = $username; session_write_close(); // save the session login_send_confirmation($user); logger('login: \'' . $username . '\' (' . $user_id . '), change password: success', WLOG_INFO, $user_id); show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('password_changed', 'loginlib')); exit; } // Invalid credentials; pretend we're busy (slow user down), increment failure count... $failure_count = login_failure_increment($_SERVER['REMOTE_ADDR'], LOGIN_PROCEDURE_CHANGE_PASSWORD, $username); login_failure_delay($_SERVER['REMOTE_ADDR']); if ($failure_count < intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_CHANGE_PASSWORD, t('invalid_credentials_please_retry', 'loginlib')); } elseif ($failure_count == intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER, t('too_many_login_attempts', 'loginlib')); } else { login_failure_blacklist_address($_SERVER['REMOTE_ADDR'], 60 * intval($CFG->login_blacklist_interval), $username); show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('too_many_change_password_attempts', 'loginlib')); } } else { show_login(LOGIN_PROCEDURE_CHANGE_PASSWORD); } exit; break; case LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER: if (isset($_POST['login_username']) && isset($_POST['login_email'])) { $username = magic_unquote($_POST['login_username']); $email = magic_unquote($_POST['login_email']); $user = authenticate_user(BY_EMAIL, $username, $email); if ($user !== FALSE) { if (login_send_laissez_passer($user)) { show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('see_mail_for_further_instructions', 'loginlib')); } else { show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('failure_sending_laissez_passer_mail', 'loginlib')); } exit; } else { // Not authenticated; pretend we're busy (slow user down), increment failure count... $failure_count = login_failure_increment($_SERVER['REMOTE_ADDR'], LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER, $username); login_failure_delay($_SERVER['REMOTE_ADDR']); if ($failure_count < intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER, t('invalid_username_email_please_retry', 'loginlib')); } elseif ($failure_count == intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('too_many_login_attempts', 'loginlib')); } else { login_failure_blacklist_address($_SERVER['REMOTE_ADDR'], 60 * intval($CFG->login_blacklist_interval), $username); show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('too_many_login_attempts', 'loginlib')); } exit; } } else { show_login(LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER); } exit; break; case LOGIN_PROCEDURE_SEND_BYPASS: if (isset($_GET['code']) && isset($_GET['username'])) { $laissez_passer = magic_unquote($_GET['code']); $username = magic_unquote($_GET['username']); } elseif (isset($_POST['login_username']) && isset($_POST['login_laissez_passer'])) { $laissez_passer = magic_unquote($_POST['login_laissez_passer']); $username = magic_unquote($_POST['login_username']); } else { show_login(LOGIN_PROCEDURE_SEND_BYPASS); exit; } // still here? Then we check the laissez_passer and send a second email to the user $user = authenticate_user(BY_LAISSEZ_PASSER, $username, $laissez_passer); if ($user !== FALSE) { login_failure_reset($_SERVER['REMOTE_ADDR']); if (login_send_bypass($user)) { show_login(LOGIN_PROCEDURE_NORMAL, t('see_mail_for_new_temporary_password', 'loginlib')); } else { show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('failure_sending_temporary_password', 'loginlib')); } } else { $failure_count = login_failure_increment($_SERVER['REMOTE_ADDR'], LOGIN_PROCEDURE_SEND_BYPASS, $username); login_failure_delay($_SERVER['REMOTE_ADDR']); if ($failure_count < intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_SEND_BYPASS, t('invalid_laissez_passer_please_retry', 'loginlib')); } elseif ($failure_count == intval($CFG->login_max_failures)) { show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('too_many_login_attempts', 'loginlib')); } else { login_failure_blacklist_address($_SERVER['REMOTE_ADDR'], 60 * intval($CFG->login_blacklist_interval), $username); show_login(LOGIN_PROCEDURE_MESSAGE_BOX, t('too_many_login_attempts', 'loginlib')); } } exit; break; case LOGIN_PROCEDURE_SHOWLOGIN: show_login(LOGIN_PROCEDURE_NORMAL, $message); exit; break; default: show_login(LOGIN_PROCEDURE_NORMAL); exit; break; } }