'persistent_login/erase', 'callback' => 'persistent_login_erase', 'access' => TRUE, 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/settings/persistent_login', 'title' => t('Persistent Login'), 'description' => t('Control Persistent Login session lifetime and restricted pages.'), 'callback' => 'drupal_get_form', 'callback arguments' => 'persistent_login_admin_settings', 'access' => user_access('administer Persistent Login'), 'type' => MENU_NORMAL_ITEM, ); } return $items; } function persistent_login_admin_settings() { return system_settings_form(persistent_login_settings()); } function persistent_login_settings() { if (!user_access('administer Persistent Login')) { return; } if (ini_get('session.cookie_lifetime') > 0) { drupal_set_message(t('Your site\'s session.cookie_lifetime PHP setting is %life. When using Persistent Login, it should be 0 so that PHP sessions end when the user closes his/her browser. You can change this setting by editing %file.', array('%life' => ini_get('session.cookie_lifetime'), '%file' => conf_path().'/settings.php')), 'error'); } $form['persistent_login_welcome'] = array( '#type' => 'checkbox', '#title' => t('Show welcome message on persistent login.'), '#default_value' => variable_get('persistent_login_welcome', TRUE), '#description' => t('If checked, the message \'Welcome back, username\' will be displayed each time a new login session is created via a Persistent Login cookie.'), ); $form['persistent_login_maxlife'] = array( '#type' => 'textfield', '#title' => t('Days to remember the user'), '#default_value' => variable_get('persistent_login_maxlife', PERSISTENT_LOGIN_MAXLIFE), '#description' => t('The maximum number of days for which a persistent login session is valid; afterwards, the user will need to log in again. Enter 0 for no expiration.'), ); $form['persistent_login_secure'] = array( '#type' => 'radios', '#title' => t('Pages which require an explicit login'), '#default_value' => variable_get('persistent_login_secure', 1), '#options' => array(t('Every page except the listed pages.'), t('Only the listed pages.')), ); $form['persistent_login_pages'] = array( '#type' => 'textarea', '#title' => t('Pages'), '#default_value' => variable_get('persistent_login_pages', PERSISTENT_LOGIN_SECURE_PATHS), '#cols' => 40, '#rows' => 5, '#description' => t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are 'blog' for the blog page, 'blog/*' for every personal blog, and 'blog*' for both. '<front>' is the front page."), ); return $form; } /** * Implementation of hook_form_alter(). */ function persistent_login_form_alter($form_id, &$form) { if (substr($form_id, 0, 10) == 'user_login') { $element = array( '#type' => 'checkbox', '#title' => t('Remember me'), ); if ($_SESSION['persistent_login_default_user']) { $form['name']['#default_value'] = $_SESSION['persistent_login_default_user']; } unset($_SESSION['persistent_login_default_user']); } // Don't show Remember Me checkbox if we're reauthenticating to // access a protected page unless I change the code to delete the PL // session if the user does not check the box. // // This variable is not unset until login succeeds so if the user // mistypes the password Remember Me will stay hidden. Since this // can only get set within a valid PL session, there is no risk of // it hiding Remember Me for a non-logged-in user. // if ($_SESSION['persistent_login_reauth']) { return; } if ($form_id == 'user_login') { $form['persistent_login'] = $element; $form['persistent_login']['#attributes'] = array('tabindex' => 3); $form['op']['#attributes'] = array('tabindex' => 4); } else if ($form_id == 'user_login_block') { /* there must be a better way! */ $tmp = $form; $form = array(); foreach ($tmp as $k => $v) { $form[$k] = $v; if ($k == 'pass') { $form['persistent_login'] = $element; } } } } /** * Implementation of hook_user(). */ function persistent_login_user($op, &$edit, &$account, $category = NULL) { global $user, $cookie_domain; switch ($op) { case 'login': if ($edit['persistent_login'] == 1) { /* set a new cookie, preserving series and expiration if present */ _persistent_login_setcookie($user, $edit); } unset($_SESSION['persistent_login_login']); // see comment in _form_alter() unset($_SESSION['persistent_login_reauth']); break; case 'logout': if (!empty($_COOKIE[PERSISTENT_LOGIN_COOKIE])) { setcookie(PERSISTENT_LOGIN_COOKIE, '', time() - 86400, '/', $cookie_domain); unset($_SESSION['persistent_login_check']); unset($_SESSION['persistent_login_login']); unset($_SESSION['persistent_login_reauth']); list($uid, $series, $token) = explode(':', $_COOKIE[PERSISTENT_LOGIN_COOKIE]); _persistent_login_invalidate('logout', 'uid=%d AND series=\'%s\'', $uid, $series); } break; case 'view': if ($user->uid == $account->uid || user_access('administer Persistent Login')) { $n = db_result(db_query('SELECT count(*) FROM {persistent_login} WHERE uid=%d AND (expires = 0 OR expires > %d)', $account->uid, time())); $items[] = array( 'title' => t('Remembered logins'), 'value' => t('@acct %n persistent login session(s) created with the "Remember Me" login option on this site. If you no longer trust the computer(s) on which these remembered sessions were created or think your account has been compromised for any reason, you can !erase_link. This will not log you out of your current session but you will have to provide your username and password to log in the next time you visit this site.', array( '@acct' => (($user->uid == $account->uid) ? t('You have') : t('User @user has', array('@user' => $account->name))), '%n' => $n, '!erase_link' => l(t('erase persistent logins now'), 'persistent_login/erase/'.$account->uid, array(), drupal_get_destination()), ) ), 'class' => 'logins', ); return ($n > 0) ? array(t('Security') => $items) : NULL; } break; case 'update': // If the password is modified, fall through to wipe all persistent logins if (empty($edit['pass'])) { break; } case 'delete': _persistent_login_invalidate($op, 'uid=%d', $account->uid); unset($_SESSION['persistent_login_check']); unset($_SESSION['persistent_login_login']); break; } } /** * Implementation of hook_cron(). */ function persistent_login_cron() { _persistent_login_invalidate('cron', 'expires > 0 AND expires < %d', time()); } function persistent_login_erase($uid = NULL) { global $user; if (!isset($uid)) { $uid = $user->uid; } if ($uid > 0 && ($user->uid == $uid || user_access('administer Persistent Login'))) { _persistent_login_invalidate('erase', 'uid=%d', $uid); } drupal_goto(); } /** * _persistent_login_check(). Do the real work. Note that we may be * in BOOTSTRAP_PAGE_CACHE mode with few modules loaded. * * If a non-logged in user has a valid Persistent Login cookie, log her in, * disable the old cookie, and issue a new one for next time. Then * reload the current page so the user is logged in from the * beginning. * * If a non-logged in user has an invalid PL cookie that indicates an * attack has occurred, panic. * * If a user logged in by Persistent Login tries to access a protected * page, redirect them to the login page. Their remembered login is * preserved, though, so they can skip the login and keep browsing * non-protected pages. */ function _persistent_login_check() { global $user; $path = isset($_GET['q']) ? $_GET['q'] : ''; // Do not interfere with login/logout pages. if ($path === 'user/login' || $path === 'logout') { return; } $now = time(); if ($user->uid == 0 && isset($_COOKIE[PERSISTENT_LOGIN_COOKIE]) && !isset($_SESSION['persistent_login_check'])) { // For efficiency, only check once per session unless something changes. $_SESSION['persistent_login_check'] = TRUE; list($uid, $series, $token) = explode(':', $_COOKIE[PERSISTENT_LOGIN_COOKIE]); $res = db_query("SELECT u.name, pl.uid, pl.series as pl_series, pl.token as pl_token, pl.expires as pl_expires FROM {persistent_login} pl INNER JOIN {users} u USING (uid) WHERE u.status = 1 AND pl.uid = %d AND pl.series = '%s'", $uid, $series); $r = db_fetch_array($res); if (!is_array($r) || count($r) == 0) { // $uid:$series is invalid return; } else if ($r['pl_expires'] > 0 && $r['pl_expires'] < time()) { // $uid:$series has expired return; } // now, any outcome requires this require_once './includes/common.inc'; require_once './includes/path.inc'; require_once './includes/theme.inc'; if ($r['pl_token'] === $token) { // The Persistent Login cookie is valid. $r is a 'user form' // that contains only name, uid, pl_series, pl_token, and // pl_expires. Add persistent_login so we and other modules can // tell what is going on. // $r['persistent_login'] = 1; // Delete the one-time use persistent login cookie. _persistent_login_invalidate('used', 'uid=%d AND series=\'%s\'', $uid, $series); // Log in the user. Use user_login_submit here so // hook_user('login') is invoked, login is watchdogged, and db // is updated. Be sure to override persistent_login_login to // TRUE (it is set to FALSE in our hook_user). // drupal_load('module', 'user'); $user = user_load(array('uid' => $r['uid'])); user_login_submit('persistent_login', $r); $_SESSION['persistent_login_login'] = TRUE; // Only welcome the user back once per session. if (empty($_SESSION['persistent_login_welcomed']) && variable_get('persistent_login_welcome', TRUE)) { drupal_set_message(t('Welcome back, %name.', array('%name' => $r['name']))); } $_SESSION['persistent_login_welcomed'] = TRUE; // Reload this page as the user. If page caching is enabled, // the user was not logged in until now and so the page may have // come from the cache. Also, some other init hook may care. // $_REQUEST['destination'] = substr(drupal_get_destination(), 12); drupal_goto(); // not reached return; } else { // The Persistent Login cookie is NOT valid, but $uid:$series // was right. This means two browsers are sharing the cookie, // so someone is cheating. Panic. // Reset PL state in $_SESSION. $d = array(); _persistent_login_invalidate('stolen', 'uid=%d', $uid); persistent_login_user('logout', $d, $user); // Delete all open sessions for this user. Use $uid from the // PL cookie, not $user->uid which is still 0. No need to // regenerate the session, user will be anonymous on next visit. sess_destroy_uid($uid); // Log the event, warn the user. watchdog('security', t('Stolen Persistent Login session for user %user detected.', array('%user' => $r['name']))); drupal_set_message(t('
SECURITY ALERT!
You previously logged in to this site and checked the Remember me box. At that time, this site stored a "login cookie" on your web browser that it uses to identify you each time you return. However, the login cookie that your browser just provided is incorrect. One possible cause of this error is that your web browser cookies have been stolen and used by someone else to impersonate you at this site.
As a precaution, we logged out all of your current sessions and deactivated all your remembered logins to this site. You can log in again now.
'), 'error'); drupal_goto(); return; } } else if (isset($_SESSION['persistent_login_login'])) { require_once './includes/common.inc'; // User is logged in only via Persistent Login. Don't let her // visit restricted pages. // $path = $_GET['q']; $page_match = _persistent_login_match($path); if ($page_match) { $_SESSION['persistent_login_default_user'] = $user->name; $user = user_load(array('uid' => 0)); unset($_SESSION['persistent_login_check']); unset($_SESSION['persistent_login_login']); $_SESSION['persistent_login_reauth'] = TRUE; unset($_REQUEST['destination']); drupal_set_message(t('Please verify your username and password to access this page.'), 'error'); drupal_goto('user/login', drupal_get_destination()); } } } function _persistent_login_setcookie($user, $edit = array()) { global $cookie_domain; // We're about to set a new PL cookie. If the user already has a PL // but $edit['pl_series'] does not exist, they got here because they // tried to access a protected page and had to reauthenticate. // Clean up the old PL series to avoid junk in the db. // if (isset($_COOKIE[PERSISTENT_LOGIN_COOKIE]) && !isset($edit['pl_series'])) { list($uid, $series, $token) = explode(':', $_COOKIE[PERSISTENT_LOGIN_COOKIE]); _persistent_login_invalidate('cleanup', 'uid=%d AND series=\'%s\'', $uid, $series); } $tok = drupal_get_token(uniqid(mt_rand(), true)); $days = variable_get('persistent_login_maxlife', PERSISTENT_LOGIN_MAXLIFE); $expires = (isset($edit['pl_expires']) ? $edit['pl_expires'] : (($days > 0) ? time() + $days * 86400 : 0)); $series = (isset($edit['pl_series']) ? $edit['pl_series'] : drupal_get_token(uniqid(mt_rand(), true))); setcookie(PERSISTENT_LOGIN_COOKIE, $user->uid .':'. $series .':'. $tok, $expires > 0 ? $expires : 2147483647, '/', $cookie_domain); db_query("INSERT INTO {persistent_login} (uid, series, token, expires) VALUES (%d, '%s', '%s', %d)", $user->uid, $series, $tok, $expires); if (db_affected_rows() != 1) { watchdog('security', t('Persistent Login FAILURE: could not insert (%user, %series, %tok, %expires)', array( '%user' => $user->name, '%series' => $series, '%tok' => $tok, '%expires' => $expires, ))); } } /** * _persistent_login_match() * * check the page past and see if it should be secure or insecure. * * @@param $path * the page of the page to check. * * @@return * 0 - page should be insecure. * 1 - page should be secure. */ function _persistent_login_match($path) { $secure = variable_get('persistent_login_secure', 1); $pages = trim(variable_get('persistent_login_pages', PERSISTENT_LOGIN_SECURE_PATHS)); if ($pages) { $front = variable_get('site_frontpage', 'node'); $regexp = ('/^(?:'. preg_replace( array( '/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\