diff -pruN squirrelmail-1.4.8.orig/config/conf.pl squirrelmail-1.4.8/config/conf.pl --- squirrelmail-1.4.8.orig/config/conf.pl 2006-08-01 07:47:32.000000000 +0200 +++ squirrelmail-1.4.8/config/conf.pl 2009-01-17 19:12:14.000000000 +0100 @@ -333,6 +333,9 @@ if ( !$sendmail_args && $sendmail_path = $sendmail_args = '-i -t'; } +# Added in 1.4.16 +$only_secure_cookies = 'true' if ( !$only_secure_cookies ); + if ( $ARGV[0] eq '--install-plugin' ) { print "Activating plugin " . $ARGV[1] . "\n"; push @plugins, $ARGV[1]; @@ -487,23 +490,24 @@ while ( ( $command ne "q" ) && ( $comman print "R Return to Main Menu\n"; } elsif ( $menu == 4 ) { print $WHT. "General Options\n" . $NRM; - print "1. Data Directory : $WHT$data_dir$NRM\n"; - print "2. Attachment Directory : $WHT$attachment_dir$NRM\n"; - print "3. Directory Hash Level : $WHT$dir_hash_level$NRM\n"; - print "4. Default Left Size : $WHT$default_left_size$NRM\n"; - print "5. Usernames in Lowercase : $WHT$force_username_lowercase$NRM\n"; - print "6. Allow use of priority : $WHT$default_use_priority$NRM\n"; - print "7. Hide SM attributions : $WHT$hide_sm_attributions$NRM\n"; - print "8. Allow use of receipts : $WHT$default_use_mdn$NRM\n"; - print "9. Allow editing of identity : $WHT$edit_identity$NRM\n"; - print " Allow editing of name : $WHT$edit_name$NRM\n"; - print " Remove username from header : $WHT$hide_auth_header$NRM\n"; - print "10. Allow server thread sort : $WHT$allow_thread_sort$NRM\n"; - print "11. Allow server-side sorting : $WHT$allow_server_sort$NRM\n"; - print "12. Allow server charset search : $WHT$allow_charset_search$NRM\n"; - print "13. Enable UID support : $WHT$uid_support$NRM\n"; - print "14. PHP session name : $WHT$session_name$NRM\n"; - print "15. Location base : $WHT$config_location_base$NRM\n"; + print "1. Data Directory : $WHT$data_dir$NRM\n"; + print "2. Attachment Directory : $WHT$attachment_dir$NRM\n"; + print "3. Directory Hash Level : $WHT$dir_hash_level$NRM\n"; + print "4. Default Left Size : $WHT$default_left_size$NRM\n"; + print "5. Usernames in Lowercase : $WHT$force_username_lowercase$NRM\n"; + print "6. Allow use of priority : $WHT$default_use_priority$NRM\n"; + print "7. Hide SM attributions : $WHT$hide_sm_attributions$NRM\n"; + print "8. Allow use of receipts : $WHT$default_use_mdn$NRM\n"; + print "9. Allow editing of identity : $WHT$edit_identity$NRM\n"; + print " Allow editing of name : $WHT$edit_name$NRM\n"; + print " Remove username from header : $WHT$hide_auth_header$NRM\n"; + print "10. Allow server thread sort : $WHT$allow_thread_sort$NRM\n"; + print "11. Allow server-side sorting : $WHT$allow_server_sort$NRM\n"; + print "12. Allow server charset search : $WHT$allow_charset_search$NRM\n"; + print "13. Enable UID support : $WHT$uid_support$NRM\n"; + print "14. PHP session name : $WHT$session_name$NRM\n"; + print "15. Location base : $WHT$config_location_base$NRM\n"; + print "16. Only secure cookies if poss. : $WHT$only_secure_cookies$NRM\n"; print "\n"; print "R Return to Main Menu\n"; } elsif ( $menu == 5 ) { @@ -718,6 +722,7 @@ while ( ( $command ne "q" ) && ( $comman elsif ( $command == 13 ) { $uid_support = command313(); } elsif ( $command == 14 ) { $session_name = command314(); } elsif ( $command == 15 ) { $config_location_base = command_config_location_base(); } + elsif ( $command == 16 ) { $only_secure_cookies = command316(); } } elsif ( $menu == 5 ) { if ( $command == 1 ) { command41(); } elsif ( $command == 2 ) { $theme_css = command42(); } @@ -2270,6 +2275,34 @@ sub command_config_location_base { } +# only_secure_cookies (since 1.4.16) +sub command316 { + print "This option allows you to specify that if a user session is initiated\n"; + print "under a secure (HTTPS, SSL-encrypted) connection, the cookies given to\n"; + print "the browser will ONLY be transmitted via a secure connection henceforth.\n\n"; + print "Generally this is a Good Thing, and should NOT be disabled. However,\n"; + print "if you intend to use the Secure Login or Show SSL Link plugins to\n"; + print "encrypt the user login, but not the rest of the SquirrelMail session,\n"; + print "this can be turned off. Think twice before doing so.\n"; + print "\n"; + + if ( lc($only_secure_cookies) eq 'true' ) { + $default_value = "y"; + } else { + $default_value = "n"; + } + print "Transmit cookies only on secure connection when available? (y/n) [$WHT$default_value$NRM]: $WHT"; + $only_secure_cookies = <STDIN>; + if ( ( $only_secure_cookies =~ /^y\n/i ) || ( ( $only_secure_cookies =~ /^\n/ ) && ( $default_value eq "y" ) ) ) { + $only_secure_cookies = 'true'; + } else { + $only_secure_cookies = 'false'; + } + return $only_secure_cookies; +} + + + #################################################################################### #### THEMES #### sub command41 { @@ -3243,6 +3276,9 @@ sub save_data { print CF "\$session_name = '$session_name';\n"; + # boolean + print CF "\$only_secure_cookies = $only_secure_cookies;\n"; + print CF "\n"; print CF "\$config_location_base = '$config_location_base';\n"; diff -pruN squirrelmail-1.4.8.orig/functions/global.php squirrelmail-1.4.8/functions/global.php --- squirrelmail-1.4.8.orig/functions/global.php 2006-07-29 10:57:52.000000000 +0200 +++ squirrelmail-1.4.8/functions/global.php 2009-01-17 19:17:54.000000000 +0100 @@ -91,6 +91,11 @@ if (isset($_SERVER['PHP_SELF'])) { require_once(SM_PATH . 'functions/strings.php'); require_once(SM_PATH . 'config/config.php'); +/** + * Detect SSL connections + */ +$is_secure_connection = is_ssl_secured_connection(); + /** set the name of the session cookie */ if(isset($session_name) && $session_name) { ini_set('session.name' , $session_name); @@ -118,6 +123,10 @@ if (!(bool)ini_get('session.use_cookies' ini_set('session.use_cookies','1'); } +/* Make sure to have $base_uri always initialized to avoid having session + cookie set twice (for $base_uri and $base_uri/src. */ +$base_uri = sqm_baseuri(); + /* convert old-style superglobals to current method * this is executed if you are running PHP 4.0.x. * it is run via a require_once directive in validate.php @@ -374,9 +383,12 @@ function sqsession_destroy() { global $base_uri; - if (isset($_COOKIE[session_name()])) setcookie(session_name(), '', 0, $base_uri); - if (isset($_COOKIE['username'])) setcookie('username', '', 0, $base_uri); - if (isset($_COOKIE['key'])) setcookie('key', '', 0, $base_uri); + if (isset($_COOKIE[session_name()])) { + sqsetcookie(session_name(), $_COOKIE[session_name()], 1, $base_uri); + sqsetcookie(session_name(), $_COOKIE[session_name()], 1, $base_uri."src/"); + } + if (isset($_COOKIE['username'])) sqsetcookie('username', '', 1, $base_uri); + if (isset($_COOKIE['key'])) sqsetcookie('key', '', 1, $base_uri); $sessid = session_id(); if (!empty( $sessid )) { @@ -399,12 +411,162 @@ function sqsession_destroy() { */ function sqsession_is_active() { + sqsession_start(); +} - $sessid = session_id(); - if ( empty( $sessid ) ) { - session_start(); +/** + * Function to start the session and store the cookie with the session_id as + * HttpOnly cookie which means that the cookie isn't accessible by javascript + * (IE6 only) + * Note that as sqsession_is_active() no longer discriminates as to when + * it calls this function, session_start() has to have E_NOTICE suppression + * (thus the @ sign). + * + * @return void + * + * @since 1.4.16 + * + */ +function sqsession_start() { + global $base_uri; + + session_set_cookie_params (0, $base_uri); + @session_start(); + // could be: sq_call_function_suppress_errors('session_start'); + $session_id = session_id(); + + // make sure 'deleted' is never a valid session identifier + if ($session_id == 'deleted') { + session_regenerate_id(); + $session_id = session_id(); + } + + // session_starts sets the sessionid cookie but without the httponly var + // setting the cookie again sets the httponly cookie attribute + // + // need to check if headers have been sent, since sqsession_is_active() + // has become just a passthru to this function, so the sqsetcookie() + // below is called every time, even after headers have already been sent + // + if (!headers_sent()) + sqsetcookie(session_name(),$session_id,false,$base_uri); +} + +/** + * Set a cookie + * + * @param string $sName The name of the cookie. + * @param string $sValue The value of the cookie. + * @param int $iExpire The time the cookie expires. This is a Unix + * timestamp so is in number of seconds since + * the epoch. + * @param string $sPath The path on the server in which the cookie + * will be available on. + * @param string $sDomain The domain that the cookie is available. + * @param boolean $bSecure Indicates that the cookie should only be + * transmitted over a secure HTTPS connection. + * @param boolean $bHttpOnly Disallow JS to access the cookie (IE6/FF2) + * @param boolean $bReplace Replace previous cookies with same name? + * + * @return void + * + * @since 1.4.16 and 1.5.1 + * + */ +function sqsetcookie($sName, $sValue='deleted', $iExpire=0, $sPath="", $sDomain="", + $bSecure=false, $bHttpOnly=true, $bReplace=false) { + + // if we have a secure connection then limit the cookies to https only. + global $is_secure_connection; + if ($sName && $is_secure_connection) + $bSecure = true; + + // admin config can override the restriction of secure-only cookies + // + // (we have to check if the value is set and default it to true if + // not because when upgrading without re-running conf.pl, it will + // not be found in config/config.php and thusly evaluate to false, + // but we want to default people who upgrade to true due to security + // implications of setting this to false) + // + global $only_secure_cookies; + if (!isset($only_secure_cookies)) $only_secure_cookies = true; + if (!$only_secure_cookies) + $bSecure = false; + + if (false && check_php_version(5,2)) { + // php 5 supports the httponly attribute in setcookie, but because setcookie seems a bit + // broken we use the header function for php 5.2 as well. We might change that later. + //setcookie($sName,$sValue,(int) $iExpire,$sPath,$sDomain,$bSecure,$bHttpOnly); + } else { + if (!empty($sDomain)) { + // Fix the domain to accept domains with and without 'www.'. + if (strtolower(substr($sDomain, 0, 4)) == 'www.') $sDomain = substr($sDomain, 4); + $sDomain = '.' . $sDomain; + + // Remove port information. + $Port = strpos($sDomain, ':'); + if ($Port !== false) $sDomain = substr($sDomain, 0, $Port); + } + if (!$sValue) $sValue = 'deleted'; + header('Set-Cookie: ' . rawurlencode($sName) . '=' . rawurlencode($sValue) + . (empty($iExpire) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $iExpire) . ' GMT') + . (empty($sPath) ? '' : '; path=' . $sPath) + . (empty($sDomain) ? '' : '; domain=' . $sDomain) + . (!$bSecure ? '' : '; secure') + . (!$bHttpOnly ? '' : '; HttpOnly'), $bReplace); } } + +/** + * Detect whether or not we have a SSL secured (HTTPS) + * connection to the browser + * + * It is thought to be so if you have 'SSLOptions +StdEnvVars' + * in your Apache configuration, + * OR if you have HTTPS set to a non-empty value (except "off") + * in your HTTP_SERVER_VARS, + * OR if you have HTTP_X_FORWARDED_PROTO=https in your HTTP_SERVER_VARS, + * OR if you are on port 443. + * + * Note: HTTP_X_FORWARDED_PROTO could be sent from the client and + * therefore possibly spoofed/hackable - for now, the + * administrator can tell SM to ignore this value by setting + * $sq_ignore_http_x_forwarded_headers to boolean TRUE in + * config/config_local.php, but in the future we may + * want to default this to TRUE and make administrators + * who use proxy systems turn it off (see 1.5.2+). + * + * Note: It is possible to run SSL on a port other than 443, and + * if that is the case, the administrator should set + * $sq_https_port to the applicable port number in + * config/config_local.php + * + * @return boolean TRUE if the current connection is SSL-encrypted; + * FALSE otherwise. + * + * @since 1.4.17 and 1.5.2 + * + */ +function is_ssl_secured_connection() +{ + global $sq_ignore_http_x_forwarded_headers, $sq_https_port; + $https_env_var = getenv('HTTPS'); + if ($sq_ignore_http_x_forwarded_headers + || !sqgetGlobalVar('HTTP_X_FORWARDED_PROTO', $forwarded_proto, SQ_SERVER)) + $forwarded_proto = ''; + if (empty($sq_https_port)) // won't work with port 0 (zero) + $sq_https_port = 443; + if ((isset($https_env_var) && strcasecmp($https_env_var, 'on') === 0) + || (sqgetGlobalVar('HTTPS', $https, SQ_SERVER) && !empty($https) + && strcasecmp($https, 'off') !== 0) + || (strcasecmp($forwarded_proto, 'https') === 0) + || (sqgetGlobalVar('SERVER_PORT', $server_port, SQ_SERVER) + && $server_port == $sq_https_port)) + return TRUE; + return FALSE; +} + // vim: et ts=4 ?> diff -pruN squirrelmail-1.4.8.orig/functions/strings.php squirrelmail-1.4.8/functions/strings.php --- squirrelmail-1.4.8.orig/functions/strings.php 2006-08-11 13:15:43.000000000 +0200 +++ squirrelmail-1.4.8/functions/strings.php 2009-01-17 19:12:14.000000000 +0100 @@ -266,7 +266,8 @@ function sqm_baseuri(){ */ function get_location () { - global $imap_server_type, $config_location_base; + global $imap_server_type, $config_location_base, + $is_secure_connection, $sq_ignore_http_x_forwarded_headers; /* Get the path, handle virtual directories */ if(strpos(php_self(), '?')) { @@ -290,21 +291,13 @@ function get_location () { /* Check if this is a HTTPS or regular HTTP request. */ $proto = 'http://'; - - /* - * If you have 'SSLOptions +StdEnvVars' in your apache config - * OR if you have HTTPS=on in your HTTP_SERVER_VARS - * OR if you are on port 443 - */ - $getEnvVar = getenv('HTTPS'); - if ((isset($getEnvVar) && strcasecmp($getEnvVar, 'on') === 0) || - (sqgetGlobalVar('HTTPS', $https_on, SQ_SERVER) && strcasecmp($https_on, 'on') === 0) || - (sqgetGlobalVar('SERVER_PORT', $server_port, SQ_SERVER) && $server_port == 443)) { + if ($is_secure_connection) $proto = 'https://'; - } /* Get the hostname from the Host header or server config. */ - if ( !sqgetGlobalVar('HTTP_X_FORWARDED_HOST', $host, SQ_SERVER) || empty($host) ) { + if ($sq_ignore_http_x_forwarded_headers + || !sqgetGlobalVar('HTTP_X_FORWARDED_HOST', $host, SQ_SERVER) + || empty($host)) { if ( !sqgetGlobalVar('HTTP_HOST', $host, SQ_SERVER) || empty($host) ) { if ( !sqgetGlobalVar('SERVER_NAME', $host, SQ_SERVER) || empty($host) ) { $host = ''; diff -pruN squirrelmail-1.4.8.orig/src/redirect.php squirrelmail-1.4.8/src/redirect.php --- squirrelmail-1.4.8.orig/src/redirect.php 2009-01-17 18:40:58.000000000 +0100 +++ squirrelmail-1.4.8/src/redirect.php 2009-01-17 19:17:54.000000000 +0100 @@ -38,7 +38,6 @@ $base_uri = sqm_baseuri(); header('Pragma: no-cache'); $location = get_location(); -session_set_cookie_params (0, $base_uri); sqsession_is_active(); sqsession_unregister ('user_is_logged_in'); @@ -61,8 +60,7 @@ if (!sqgetGlobalVar('mailtodata', $mailt set_up_language($squirrelmail_language, true); /* Refresh the language cookie. */ -setcookie('squirrelmail_language', $squirrelmail_language, time()+2592000, - $base_uri); +sqsetcookie('squirrelmail_language', $squirrelmail_language, time()+2592000, $base_uri); if (!isset($login_username)) { include_once(SM_PATH . 'functions/display_messages.php' ); @@ -73,6 +71,9 @@ if (!isset($login_username)) { if (!sqsession_is_registered('user_is_logged_in')) { do_hook ('login_before'); + // make sure to regenerate session id upon user login + session_regenerate_id(); + $onetimepad = OneTimePadCreate(strlen($secretkey)); $key = OneTimePadEncrypt($secretkey, $onetimepad); sqsession_register($onetimepad, 'onetimepad'); @@ -96,7 +97,7 @@ if (!sqsession_is_registered('user_is_lo $username = $login_username; sqsession_register ($username, 'username'); - setcookie('key', $key, 0, $base_uri); + sqsetcookie('key', $key, 0, $base_uri); do_hook ('login_verified'); } diff -pruN squirrelmail-1.4.8.orig/src/webmail.php squirrelmail-1.4.8/src/webmail.php --- squirrelmail-1.4.8.orig/src/webmail.php 2009-01-17 18:40:58.000000000 +0100 +++ squirrelmail-1.4.8/src/webmail.php 2009-01-17 19:12:14.000000000 +0100 @@ -71,7 +71,7 @@ do_hook('webmail_top'); */ $my_language = getPref($data_dir, $username, 'language'); if ($my_language != $squirrelmail_language) { - setcookie('squirrelmail_language', $my_language, time()+2592000, $base_uri); + sqsetcookie('squirrelmail_language', $my_language, time()+2592000, $base_uri); } set_up_language(getPref($data_dir, $username, 'language'));