=== modified file 'includes/bootstrap.inc' --- includes/bootstrap.inc 2009-08-04 04:02:25 +0000 +++ includes/bootstrap.inc 2009-08-04 05:37:06 +0000 @@ -518,7 +518,7 @@ function drupal_settings_initialize() { global $base_url, $base_path, $base_root; // Export the following settings.php variables to the global namespace - global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url; + global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $is_https, $base_secure_url, $base_insecure_url; $conf = array(); if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) { @@ -528,6 +528,7 @@ function drupal_settings_initialize() { if (isset($base_url)) { // Parse fixed base URL from settings.php. $parts = parse_url($base_url); + $http_protocol = $parts['scheme']; if (!isset($parts['path'])) { $parts['path'] = ''; } @@ -537,9 +538,10 @@ function drupal_settings_initialize() { } else { // Create base URL - $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'; + $http_protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'; + $base_root = $http_protocol . '://' . $_SERVER['HTTP_HOST']; - $base_url = $base_root .= '://' . $_SERVER['HTTP_HOST']; + $base_url = $base_root; // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not // be modified by a visitor. @@ -552,6 +554,9 @@ function drupal_settings_initialize() { $base_path = '/'; } } + $is_https = $http_protocol == 'https'; + $base_secure_url = str_replace('http://', 'https://', $base_url); + $base_insecure_url = str_replace('https://', 'http://', $base_url); if ($cookie_domain) { // If the user specifies the cookie domain, also use it for session name. @@ -566,15 +571,6 @@ function drupal_settings_initialize() { $cookie_domain = check_plain($_SERVER['HTTP_HOST']); } } - // To prevent session cookies from being hijacked, a user can configure the - // SSL version of their website to only transfer session cookies via SSL by - // using PHP's session.cookie_secure setting. The browser will then use two - // separate session cookies for the HTTPS and HTTP versions of the site. So we - // must use different session identifiers for HTTPS and HTTP to prevent a - // cookie collision. - if (ini_get('session.cookie_secure')) { - $session_name .= 'SSL'; - } // Strip leading periods, www., and port numbers from cookie domain. $cookie_domain = ltrim($cookie_domain, '.'); if (strpos($cookie_domain, 'www.') === 0) { @@ -587,7 +583,17 @@ function drupal_settings_initialize() { if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) { ini_set('session.cookie_domain', $cookie_domain); } - session_name('SESS' . md5($session_name)); + // To prevent session cookies from being hijacked, a user can configure the + // SSL version of their website to only transfer session cookies via SSL by + // using PHP's session.cookie_secure setting. The browser will then use two + // separate session cookies for the HTTPS and HTTP versions of the site. So we + // must use different session identifiers for HTTPS and HTTP to prevent a + // cookie collision. + if ($is_https) { + ini_set('session.cookie_secure', TRUE); + } + $prefix = ini_get('session.cookie_secure') ? 'SSESS' : 'SESS'; + session_name($prefix . md5($session_name)); } /** === modified file 'includes/common.inc' --- includes/common.inc 2009-08-04 04:02:25 +0000 +++ includes/common.inc 2009-08-04 05:37:06 +0000 @@ -338,7 +338,7 @@ function drupal_goto($path = '', $query extract(parse_url(urldecode($_REQUEST['destination']))); } - $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE)); + $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE, 'secure' => FALSE)); // Allow modules to react to the end of the page request before redirecting. // We do not want this while running update.php. @@ -2023,6 +2023,10 @@ function _format_date_callback(array $ma * - 'language' * An optional language object. Used to build the URL to link to and * look up the proper alias for the link. + * - 'secure' + * Whether this URL should point to a secure location. If not specified, + * the current scheme is used, so the user stays on http or https + * respectively. TRUE enforces HTTPS and FALSE enforces HTTP. * - 'base_url' * Only used internally, to modify the base URL when a language dependent * URL requires so. @@ -2036,12 +2040,14 @@ function _format_date_callback(array $ma * alternative than url(). */ function url($path = NULL, array $options = array()) { + global $is_https; // Merge in defaults. $options += array( 'fragment' => '', 'query' => '', 'absolute' => FALSE, 'alias' => FALSE, + 'secure' => NULL, 'prefix' => '' ); if (!isset($options['external'])) { @@ -2089,9 +2095,19 @@ function url($path = NULL, array $option $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : ''; } + // The base_url might be rewritten from the language rewrite in domain mode. if (!isset($options['base_url'])) { - // The base_url might be rewritten from the language rewrite in domain mode. - $options['base_url'] = $base_url; + if (isset($options['secure']) && variable_get('https', FALSE)) { + if ($options['secure'] === TRUE) { + $options['base_url'] = $base_secure_url; + } + elseif ($options['secure'] === FALSE) { + $options['base_url'] = $base_insecure_url; + } + } + else { + $options['base_url'] = $base_url; + } } // Preserve the original path before aliasing. === modified file 'includes/session.inc' --- includes/session.inc 2009-07-01 12:47:30 +0000 +++ includes/session.inc 2009-08-03 00:37:39 +0000 @@ -66,7 +66,7 @@ function _drupal_session_close() { * was found or the user is anonymous. */ function _drupal_session_read($sid) { - global $user; + global $user, $is_https; // Write and Close handlers are called after destructing objects // since PHP 5.0.5. @@ -76,14 +76,29 @@ function _drupal_session_read($sid) { // Handle the case of first time visitors and clients that don't store // cookies (eg. web crawlers). - if (!isset($_COOKIE[session_name()])) { + $insecure_session_name = substr(session_name(), 1); + if (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name])) { $user = drupal_anonymous_user(); return ''; } // Otherwise, if the session is still active, we have a record of the - // client's session in the database. - $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject(); + // client's session in the database. If it's HTTPS then we are either have + // a HTTPS session or we are about to log in so we check the sessions table + // for an anonymous session wth the non-HTTPS-only cookie. + if ($is_https) { + $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject(); + if (!$user) { + if (isset($_COOKIE[$insecure_session_name])) { + $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array( + ':sid' => $_COOKIE[$insecure_session_name])) + ->fetchObject(); + } + } + } + else { + $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject(); + } // We found the client's session record and they are an authenticated user. if ($user && $user->uid > 0) { @@ -122,22 +137,27 @@ function _drupal_session_read($sid) { * This function will always return TRUE. */ function _drupal_session_write($sid, $value) { - global $user; + global $user, $is_https; if (!drupal_save_session()) { // We don't have anything to do if we are not allowed to save the session. return; } + $fields = array( + 'uid' => $user->uid, + 'cache' => isset($user->cache) ? $user->cache : 0, + 'hostname' => ip_address(), + 'session' => $value, + 'timestamp' => REQUEST_TIME, + ); + $insecure_session_name = substr(session_name(), 1); + if ($is_https && isset($_COOKIE[$insecure_session_name])) { + $fields['sid'] = $_COOKIE[$insecure_session_name]; + } db_merge('sessions') - ->key(array('sid' => $sid)) - ->fields(array( - 'uid' => $user->uid, - 'cache' => isset($user->cache) ? $user->cache : 0, - 'hostname' => ip_address(), - 'session' => $value, - 'timestamp' => REQUEST_TIME, - )) + ->key(array($is_https ? 'ssid' : 'sid' => $sid)) + ->fields($fields) ->execute(); // Last access time is updated no more frequently than once every 180 seconds. @@ -246,7 +266,14 @@ function drupal_session_started($set = N * Called when an anonymous user becomes authenticated or vice-versa. */ function drupal_session_regenerate() { - global $user; + global $user, $is_https; + if ($is_https && variable_get('https', FALSE)) { + $insecure_session_name = substr(session_name(), 1); + $params = session_get_cookie_params(); + $session_id = md5(uniqid(mt_rand(), TRUE)); + setcookie($insecure_session_name, $session_id, REQUEST_TIME + $params['lifetime'], $params['path'], $params['domain'], FALSE, $params['httponly']); + $_COOKIE[$insecure_session_name] = $session_id; + } if (drupal_session_started()) { $old_session_id = session_id(); @@ -264,7 +291,7 @@ function drupal_session_regenerate() { if (isset($old_session_id)) { db_update('sessions') ->fields(array( - 'sid' => session_id() + $is_https ? 'ssid' : 'sid' => session_id() )) ->condition('sid', $old_session_id) ->execute(); @@ -304,11 +331,11 @@ function drupal_session_count($timestamp * Session ID. */ function _drupal_session_destroy($sid) { - global $user; + global $user, $is_https; // Delete session data. db_delete('sessions') - ->condition('sid', $sid) + ->condition($is_https ? 'ssid' : 'sid', $sid) ->execute(); // Reset $_SESSION and $user to prevent a new session from being started @@ -316,11 +343,18 @@ function _drupal_session_destroy($sid) { $_SESSION = array(); $user = drupal_anonymous_user(); - // Unset the session cookie. - if (isset($_COOKIE[session_name()])) { + // Unset the session cookies. + _drupal_session_delete_cookie(session_name()); + if ($is_https) { + _drupal_session_delete_cookie(substr(session_name(), 1), TRUE); + } +} + +function _drupal_session_delete_cookie($name, $force_insecure = FALSE) { + if (isset($_COOKIE[$name])) { $params = session_get_cookie_params(); - setcookie(session_name(), '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']); - unset($_COOKIE[session_name()]); + setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], !$force_insecure && $params['secure'], $params['httponly']); + unset($_COOKIE[$name]); } } === modified file 'modules/system/system.install' --- modules/system/system.install 2009-08-03 03:04:33 +0000 +++ modules/system/system.install 2009-08-04 05:37:07 +0000 @@ -1273,6 +1273,13 @@ function system_schema() { 'not null' => TRUE, 'default' => '', ), + 'ssid' => array( + 'description' => "Unique key: Secure session ID. The value is generated by PHP's Session API.", + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), 'hostname' => array( 'description' => 'The IP address that last used this session ID (sid).', 'type' => 'varchar', @@ -1304,6 +1311,9 @@ function system_schema() { 'timestamp' => array('timestamp'), 'uid' => array('uid'), ), + 'unique keys' => array( + 'ssid' => array('ssid'), + ), 'foreign keys' => array( 'uid' => array('users' => 'uid'), ), === modified file 'modules/user/user.module' --- modules/user/user.module 2009-07-31 19:01:01 +0000 +++ modules/user/user.module 2009-08-03 00:41:07 +0000 @@ -985,7 +985,7 @@ function user_user_categories() { function user_login_block() { $form = array( - '#action' => url($_GET['q'], array('query' => drupal_get_destination())), + '#action' => url($_GET['q'], array('secure' => TRUE, 'query' => drupal_get_destination())), '#id' => 'user-login-form', '#validate' => user_login_default_validators(), '#submit' => array('user_login_submit'),