/** 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; } }
/** save basic properties of user account * * @param int $user_id the account to save (pkey in users table) * @uses $WAS_SCRIPT_NAME */ function user_save_basic($user_id) { global $WAS_SCRIPT_NAME; $user_id = intval($user_id); // // 2 -- validate the data // $invalid = FALSE; $dialogdef = $this->get_dialogdef_edit_user($user_id); // // 2A -- check for generic errors (string too short, number too small, etc) if (!dialog_validate($dialogdef)) { $invalid = TRUE; } // 2B -- additional check: unique username $record = db_select_single_record('users', 'user_id', array('username' => $dialogdef['username']['value'])); if ($record !== FALSE && intval($record['user_id']) != $user_id) { // Oops, a record with that username already exists and it's not us. Go flag error ++$dialogdef['username']['errors']; $fname = $this->get_fname($dialogdef['username']); $dialogdef['username']['error_messages'][] = t('validate_not_unique', '', array('{FIELD}' => $fname)); $invalid = TRUE; } // 2C -- additional check: valid password $password1 = $dialogdef['user_password1']['value']; $password2 = $dialogdef['user_password2']['value']; if (!empty($password1) || !empty($password2)) { if ($password1 != $password2) { $params = array('{FIELD1}' => $this->get_fname($dialogdef['user_password1']), '{FIELD2}' => $this->get_fname($dialogdef['user_password2'])); ++$dialogdef['user_password1']['errors']; ++$dialogdef['user_password2']['errors']; $dialogdef['user_password1']['error_messages'][] = t('validate_different_passwords', '', $params); $dialogdef['user_password1']['value'] = ''; $dialogdef['user_password2']['value'] = ''; $invalid = TRUE; } elseif (!acceptable_new_password($password1, $password2)) { $params = array('{MIN_LENGTH}' => MINIMUM_PASSWORD_LENGTH, '{MIN_LOWER}' => MINIMUM_PASSWORD_LOWERCASE, '{MIN_UPPER}' => MINIMUM_PASSWORD_UPPERCASE, '{MIN_DIGIT}' => MINIMUM_PASSWORD_DIGITS, '{FIELD}' => $this->get_fname($dialogdef['user_password1'])); ++$dialogdef['user_password1']['errors']; $dialogdef['user_password1']['error_messages'][] = t('validate_bad_password', '', $params); $params['{FIELD}'] = $this->get_fname($dialogdef['user_password2']); ++$dialogdef['user_password2']['errors']; $dialogdef['user_password1']['value'] = ''; $dialogdef['user_password2']['value'] = ''; $invalid = TRUE; } } // 2D -- if there were any errors go redo dialog while keeping data already entered if ($invalid) { foreach ($dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $this->output->add_message($item['error_messages']); } } $params = $this->get_user_names($user_id); $this->output->add_content('<h2>' . t('usermanager_edit_user_header', 'admin', $params) . '</h2>'); $this->output->add_content(t('usermanager_edit_user_explanation', 'admin', $params)); $href = href($WAS_SCRIPT_NAME, $this->a_params(TASK_USER_SAVE, $user_id)); if ($dialogdef !== FALSE) { $this->output->add_content(dialog_quickform($href, $dialogdef)); $this->show_breadcrumbs_user($user_id); } else { $this->output->add_message(t('error_retrieving_data', 'admin')); $this->show_menu_user($user_id, TASK_USER_EDIT); } return; } // 3 -- Now actually save the data which we just validated $fields = array('username' => $dialogdef['username']['value'], 'bypass_mode' => FALSE, 'bypass_hash' => NULL, 'bypass_expiry' => NULL, 'full_name' => $dialogdef['user_fullname']['value'], 'email' => $dialogdef['user_email']['value'], 'is_active' => $dialogdef['user_is_active']['value'] == 1 ? TRUE : FALSE, 'redirect' => $dialogdef['user_redirect']['value'], 'language_key' => $dialogdef['user_language_key']['value'], 'editor' => $dialogdef['user_editor']['value'], 'skin' => $dialogdef['user_skin']['value']); if (!empty($password1)) { $new_salt = password_salt(); $new_password = $password1; $fields['salt'] = $new_salt; $fields['password_hash'] = password_hash($new_salt, $new_password); } $params = array('{USERNAME}' => $dialogdef['username']['value'], '{FULL_NAME}' => $dialogdef['user_fullname']['value']); if (db_update('users', $fields, array('user_id' => $user_id)) === FALSE) { $this->output->add_message(t('usermanager_save_user_failure', 'admin', $params)); logger("usermanager->user_save(): error saving data user '{$user_id}': " . db_errormessage()); } else { $this->output->add_message(t('usermanager_save_user_success', 'admin', $params)); logger("usermanager->user_save(): success saving changes to '{$user_id}' in 'users'", WLOG_DEBUG); } $this->users_overview(); return; }