Selaa lähdekoodia

replacing skip_SM_header with two different options. If admins want to shoot
their own foot, they have to do that themselves and remove headers
completely by modifying SquirrelMail scripts.

tokul 20 vuotta sitten
vanhempi
commit
574965eb4f
6 muutettua tiedostoa jossa 319 lisäystä ja 38 poistoa
  1. 6 0
      ChangeLog
  2. 95 9
      class/deliver/Deliver.class.php
  3. 64 20
      config/conf.pl
  4. 30 9
      config/config_default.php
  5. 104 0
      contrib/decrypt_headers.php
  6. 20 0
      functions/strings.php

+ 6 - 0
ChangeLog

@@ -342,6 +342,12 @@ Version 1.5.1 -- CVS
     Fixed possible php script errors caused by $SQSPELL_APP configuration 
     variable changes. Removed $SQSPELL_EREG configuration option. Plugin's
     version increased to 0.5.
+  - $skip_SM_header option was replaced with $encode_header_key and 
+    $hide_auth_header options. First option allows to encode user's information
+    with provided encryption key (set in 2. Server settings -> B. Update SMTP / 
+    Sendmail settings). Second option allows to disable authenticated user part
+    in Received: header, when user can't force used email address. It is set in
+    4. General Options -> 9. Allow editing of identity.
 
 Version 1.5.0 - 2 February 2004
 -------------------------------

+ 95 - 9
class/deliver/Deliver.class.php

@@ -373,7 +373,7 @@ class Deliver {
      * @return string $header
      */
     function prepareRFC822_Header($rfc822_header, $reply_rfc822_header, &$raw_length) {
-        global $domain, $version, $username, $skip_SM_header;
+        global $domain, $version, $username, $encode_header_key, $edit_identity, $hide_auth_header;
 
         /* if server var SERVER_NAME not available, use $domain */
         if(!sqGetGlobalVar('SERVER_NAME', $SERVER_NAME, SQ_SERVER)) {
@@ -391,8 +391,14 @@ class Deliver {
         /* This creates an RFC 822 date */
         $date = date('D, j M Y H:i:s ', mktime()) . $this->timezone();
         /* Create a message-id */
-        $message_id = '<' . $REMOTE_PORT . '.' . $REMOTE_ADDR . '.';
-        $message_id .= time() . '.squirrel@' . $SERVER_NAME .'>';
+        $message_id = '<' . $REMOTE_PORT . '.';
+        if (isset($encode_header_key) && trim($encode_header_key)!='') {
+            // use encrypted form of remote address
+            $message_id.= OneTimePadEncrypt($this->ip2hex($REMOTE_ADDR),base64_encode($encode_header_key));
+        } else {
+            $message_id.= $REMOTE_ADDR;
+        }
+        $message_id .= '.' . time() . '.squirrel@' . $SERVER_NAME .'>';
         /* Make an RFC822 Received: line */
         if (isset($REMOTE_HOST)) {
             $received_from = "$REMOTE_HOST ([$REMOTE_ADDR])";
@@ -406,13 +412,32 @@ class Deliver {
             $received_from .= " (proxying for $HTTP_X_FORWARDED_FOR)";
         }
         $header = array();
-        if ( !isset($skip_SM_header) || !$skip_SM_header )
-        {
-          $header[] = "Received: from $received_from" . $rn;
-          $header[] = "        (SquirrelMail authenticated user $username)" . $rn;
-          $header[] = "        by $SERVER_NAME with HTTP;" . $rn;
-          $header[] = "        $date" . $rn;
+
+        /**
+         * SquirrelMail header
+         *
+         * This Received: header provides information that allows to track
+         * user and machine that was used to send email. Don't remove it
+         * unless you understand all possible forging issues or your
+         * webmail installation does not prevent changes in user's email address.
+         * See SquirrelMail bug tracker #847107 for more details about it.
+         */
+        if (isset($encode_header_key) && 
+            trim($encode_header_key)!='') {
+            // use encoded headers, if encryption key is set and not empty
+            $header[].= 'X-Squirrel-UserHash: '.OneTimePadEncrypt($username,base64_encode($encode_header_key)).$rn;
+            $header[].= 'X-Squirrel-FromHash: '.OneTimePadEncrypt($this->ip2hex($REMOTE_ADDR),base64_encode($encode_header_key)).$rn;
+            if (isset($HTTP_X_FORWARDED_FOR))
+                $header[].= 'X-Squirrel-ProxyHash:'.OneTimePadEncrypt($this->ip2hex($HTTP_X_FORWARDED_FOR),base64_encode($encode_header_key)).$rn;
+        } else {
+            // use default received headers
+            $header[] = "Received: from $received_from" . $rn;
+            if ($edit_identity || ! isset($hide_auth_header) || ! $hide_auth_header)
+                $header[] = "        (SquirrelMail authenticated user $username)" . $rn;
+            $header[] = "        by $SERVER_NAME with HTTP;" . $rn;
+            $header[] = "        $date" . $rn;
         }
+
         /* Insert the rest of the header fields */
         $header[] = 'Message-ID: '. $message_id . $rn;
         if ($reply_rfc822_header->message_id) {
@@ -697,5 +722,66 @@ class Deliver {
         trim($refer);
         return $refer;
     }
+
+    /**
+     * Converts ip address to hexadecimal string
+     *
+     * Function is used to convert ipv4 and ipv6 addresses to hex strings.
+     * It removes all delimiter symbols from ip addresses, converts decimal
+     * ipv4 numbers to hex and pads strings in order to present full length
+     * address. ipv4 addresses are represented as 8 byte strings, ipv6 addresses 
+     * are represented as 32 byte string.
+     *
+     * If function fails to detect address format, it returns unprocessed string.
+     * @param string $string ip address string
+     * @return string processed ip address string
+     * @since 1.5.1
+     */
+    function ip2hex($string) {
+        if (preg_match("/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/",$string,$match)) {
+            // ipv4 address
+            $ret = str_pad(dechex($match[1]),2,'0',STR_PAD_LEFT)
+                . str_pad(dechex($match[2]),2,'0',STR_PAD_LEFT)
+                . str_pad(dechex($match[3]),2,'0',STR_PAD_LEFT)
+                . str_pad(dechex($match[4]),2,'0',STR_PAD_LEFT);
+        } elseif (preg_match("/^([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)$/i",$string,$match)) {
+            // full ipv6 address
+            $ret = str_pad($match[1],4,'0',STR_PAD_LEFT)
+                . str_pad($match[2],4,'0',STR_PAD_LEFT)
+                . str_pad($match[3],4,'0',STR_PAD_LEFT)
+                . str_pad($match[4],4,'0',STR_PAD_LEFT)
+                . str_pad($match[5],4,'0',STR_PAD_LEFT)
+                . str_pad($match[6],4,'0',STR_PAD_LEFT)
+                . str_pad($match[7],4,'0',STR_PAD_LEFT)
+                . str_pad($match[8],4,'0',STR_PAD_LEFT);
+        } elseif (preg_match("/^\:\:([0-9a-h\:]+)$/i",$string,$match)) {
+            // short ipv6 with all starting symbols nulled
+            $aAddr=explode(':',$match[1]);
+            $ret='';
+            foreach ($aAddr as $addr) {
+                $ret.=str_pad($addr,4,'0',STR_PAD_LEFT);
+            }
+            $ret=str_pad($ret,32,'0',STR_PAD_LEFT);
+        } elseif (preg_match("/^([0-9a-h\:]+)::([0-9a-h\:]+)$/i",$string,$match)) {
+            // short ipv6 with middle part nulled
+            $aStart=explode(':',$match[1]);
+            $sStart='';
+            foreach($aStart as $addr) {
+                $sStart.=str_pad($addr,4,'0',STR_PAD_LEFT);
+            }
+            $aEnd = explode(':',$match[2]);
+            $sEnd='';
+            foreach($aEnd as $addr) {
+                $sEnd.=str_pad($addr,4,'0',STR_PAD_LEFT);
+            }
+            $ret = $sStart
+                . str_pad('',(32 - strlen($sStart . $sEnd)),'0',STR_PAD_LEFT)
+                . $sEnd;
+        } else {
+            // unknown addressing
+            $ret = $string;
+        }
+        return $ret;
+    }
 }
 ?>

+ 64 - 20
config/conf.pl

@@ -337,6 +337,8 @@ $addrbook_global_listing = 'false'      if ( !$addrbook_global_listing );
 $abook_global_file = ''                 if ( !$abook_global_file);
 $abook_global_file_writeable = 'false'  if ( !$abook_global_file_writeable);
 $abook_global_file_listing = 'true'     if ( !$abook_global_file_listing );
+$encode_header_key = ''                 if ( !$encode_header_key );
+$hide_auth_header = 'false'             if ( !$hide_auth_header );
 
 if ( $ARGV[0] eq '--install-plugin' ) {
     print "Activating plugin " . $ARGV[1] . "\n";
@@ -430,7 +432,7 @@ while ( ( $command ne "q" ) && ( $command ne "Q" ) && ( $command ne ":q" ) ) {
           if ( lc($useSendmail) eq 'true' ) {
             print $WHT . "Sendmail" . $NRM . "\n--------\n";
             print "4.   Sendmail Path         : $WHT$sendmail_path$NRM\n";
-            print "5.   Suppress SM header    : $WHT$skip_SM_header$NRM\n";
+            print "5.   Header encryption key : $WHT$encode_header_key$NRM\n";
             print "\n";
           } else {
             print $WHT . "SMTP Settings" . $NRM . "\n-------------\n";
@@ -439,7 +441,7 @@ while ( ( $command ne "q" ) && ( $command ne "Q" ) && ( $command ne ":q" ) ) {
             print "6.   POP before SMTP       : $WHT$pop_before_smtp$NRM\n";
             print "7.   SMTP Authentication   : $WHT$smtp_auth_mech$NRM\n";
             print "8.   Secure SMTP (TLS)     : $WHT$use_smtp_tls$NRM\n";
-            print "9.   Suppress SM header    : $WHT$skip_SM_header$NRM\n";
+            print "9.   Header encryption key : $WHT$encode_header_key$NRM\n";
             print "\n";
           }
         }
@@ -500,7 +502,9 @@ while ( ( $command ne "q" ) && ( $command ne "Q" ) && ( $command ne ":q" ) ) {
         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/$WHT$edit_name$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";
@@ -692,14 +696,15 @@ while ( ( $command ne "q" ) && ( $command ne "Q" ) && ( $command ne ":q" ) ) {
               elsif ( $command == 8 )  { $imap_server_type       = command19(); }
               elsif ( $command == 9 )  { $optional_delimiter     = command111(); }
             } elsif ( $show_smtp_settings && lc($useSendmail) eq 'true' ) {
-              if ( $command == 4 )  { $sendmail_path          = command15(); }
+              if    ( $command == 4 )  { $sendmail_path          = command15(); }
+              elsif ( $command == 5 )  { $encode_header_key      = command114(); }
             } elsif ( $show_smtp_settings ) {
               if    ( $command == 4 )  { $smtpServerAddress      = command16(); }
               elsif ( $command == 5 )  { $smtpPort               = command17(); }
               elsif ( $command == 6 )  { $pop_before_smtp        = command18a(); }
               elsif ( $command == 7 )  { $smtp_auth_mech    = command112b(); }
               elsif ( $command == 8 )  { $use_smtp_tls      = command113("SMTP",$use_smtp_tls); }
-              elsif ( $command == 9 )  { $skip_SM_header    = command114(); }
+              elsif ( $command == 9 )  { $encode_header_key      = command114(); }
             }
         } elsif ( $menu == 3 ) {
             if    ( $command == 1 )  { $default_folder_prefix          = command21(); }
@@ -1358,20 +1363,28 @@ sub command113 {
     return $default_val;
 }
 
+# $encode_header_key
 sub command114{
-    print "\nUse this to suppress insertion of SquirrelMail Received: headers\n";
-    print "in outbound messages.\n\n";
-
-    $YesNo = 'n';
-    $YesNo = 'y' if ( lc($skip_SM_header) eq 'true' );
-
-    print "Suppress SM header (y/n) [$WHT$YesNo$NRM]: $WHT";
-    $new_skip_SM_header = <STDIN>;
-    chomp($new_skip_SM_header);
-
-    return 'true'  if ( lc($new_skip_SM_header) eq 'y' );
-    return 'false'  if ( lc($new_skip_SM_header) eq 'n' );
-    return $skip_SM_header;
+    print "Encryption key allows to hide SquirrelMail Received: headers\n";
+    print "in outbound messages. Interface uses encryption key to encode\n";
+    print "username, remote address and proxied address, then stores encoded\n";
+    print "information in X-Squirrel-* headers.\n";
+    print "\n";
+    print "Warning: used encryption function is not bulletproof. When used\n";
+    print "with static encryption keys, it provides only minimal security\n";
+    print "measures and information can be decoded quickly.\n";
+    print "\n";
+    print "Encoded information can be decoded with decrypt_headers.php script\n";
+    print "from SquirrelMail contrib/ directory.\n";
+    print "\n";
+    print "Enter encryption key: ";
+    $new_encode_header_key = <STDIN>;
+    if ( $new_encode_header_key eq "\n" ) {
+        $new_encode_header_key = $encode_header_key;
+    } else {
+        $new_encode_header_key =~ s/[\r\n]//g;
+    }
+    return $new_encode_header_key;
 }
 
 # MOTD
@@ -2079,6 +2092,7 @@ sub command39 {
     return 'false';
 }
 
+
 sub command310 {
     print "This allows you to prevent the editing of the user's name and ";
     print "email address. This is mainly useful when used with the ";
@@ -2095,9 +2109,11 @@ sub command310 {
     if ( ( $new_edit =~ /^y\n/i ) || ( ( $new_edit =~ /^\n/ ) && ( $default_value eq "y" ) ) ) {
         $edit_identity = 'true';
         $edit_name = 'true';
+        $hide_auth_header = 'false';
     } else {
         $edit_identity = 'false';
         $edit_name = command311();
+        $hide_auth_header = command311b();
     }
     return $edit_identity;
 }
@@ -2123,6 +2139,32 @@ sub command311 {
     return $edit_name;
 }
 
+sub command311b {
+    print "SquirrelMail adds username information to every sent email.";
+    print "It is done in order to prevent possible sender forging when ";
+    print "end users are allowed to change their email and name ";
+    print "information.\n";
+    print "\n";
+    print "You can disable this header, if you think that it violates ";
+    print "user's privacy or security. Please note, that setting will ";
+    print "work only when users are not allowed to change their identity.\n";
+    print "\n";
+
+    if ( lc($hide_auth_header) eq "true" ) {
+        $default_value = "y";
+    } else {
+        $default_value = "n";
+    }
+    print "Remove username from email headers? (y/n) [$WHT$default_value$NRM]: $WHT";
+    $new_header = <STDIN>;
+    if ( ( $new_header =~ /^y\n/i ) || ( ( $new_header =~ /^\n/ ) && ( $default_value eq "y" ) ) ) {
+        $hide_auth_header = "true";
+    } else {
+        $hide_auth_header = "false";
+    }
+    return $edit_name;
+}
+
 sub command312 {
     print "This option allows you to choose if users can use thread sorting\n";
     print "Your IMAP server must support the THREAD command for this to work\n";
@@ -3255,8 +3297,8 @@ sub save_data {
         print CF "\$invert_time            = $invert_time;\n";
         # string
         print CF "\$optional_delimiter     = '$optional_delimiter';\n";
-        #boolean
-        print CF "\$skip_SM_header         = $skip_SM_header;\n";
+        # string
+        print CF "\$encode_header_key      = '$encode_header_key';\n";
         print CF "\n";
 
     # string
@@ -3318,6 +3360,8 @@ sub save_data {
         print CF "\$edit_identity            = $edit_identity;\n";
     # boolean
         print CF "\$edit_name                = $edit_name;\n";
+    # boolean
+        print CF "\$hide_auth_header         = $hide_auth_header;\n";
     # boolean
         print CF "\$allow_thread_sort        = $allow_thread_sort;\n";
     # boolean

+ 30 - 9
config/config_default.php

@@ -143,18 +143,23 @@ $smtpServerAddress = 'localhost';
 $smtpPort = 25;
 
 /**
- * SquirrelMail header control
+ * SquirrelMail header encryption
  *
- * Option can be used to disable Received: headers added by SquirrelMail.
- * This can increase user's privacy and solve problems with spam filters
- * that increase spam marks for dynamic dialup addresses.
+ * Encryption key allows to hide SquirrelMail Received: headers
+ * in outbound messages. Interface uses encryption key to encode
+ * username, remote address and proxied address, then stores encoded
+ * information in X-Squirrel-* headers.
  *
- * If admin enables this setting, system should have some logging facility
- * or other tools to control users. SquirrelMail's Received: header provides
- * information, that can't be forged by webmail user.
- * @global bool $skip_SM_header
+ * Warning: used encryption function is not bulletproof. When used
+ * with static encryption keys, it provides only minimal security
+ * measures and information can be decoded quickly.
+ *
+ * Encoded information can be decoded with decrypt_headers.php script
+ * from SquirrelMail contrib/ directory.
+ * @global string $encode_header_key
+ * @since 1.5.1
  */
-$skip_SM_header = false;
+$encode_header_key = '';
 
 /**
  * Path to Sendmail
@@ -194,6 +199,7 @@ $imapPort = 143;
  *   macosx
  *   hmailserver
  *   mercury32
+ *   dovecot
  *   other
  *
  * Please note that this changes only some of server settings.
@@ -525,6 +531,21 @@ $default_use_mdn = true;
 $edit_identity = true;
 $edit_name = true;
 
+/**
+ * SquirrelMail adds username information to every sent email.
+ * It is done in order to prevent possible sender forging when 
+ * end users are allowed to change their email and name 
+ * information.
+ *
+ * You can disable this header, if you think that it violates
+ * user's privacy or security. Please note, that setting will
+ * work only when users are not allowed to change their identity.
+ *
+ * See SquirrelMail bug tracker #847107 for more details about it.
+ * @global bool $hide_auth_header
+ */
+$hide_auth_header = false;
+
 /**
  * Server Side Threading Control
  *

+ 104 - 0
contrib/decrypt_headers.php

@@ -0,0 +1,104 @@
+<?php
+/**
+ * Script provides form to decode encrypted header information.
+ *
+ * Copyright (c) 2005 The SquirrelMail Project Team
+ * This file is part of SquirrelMail webmail interface.
+ *
+ * SquirrelMail is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * SquirrelMail is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SquirrelMail; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @version $Id$
+ * @package squirrelmail
+ */
+
+/**
+ * Set constant to path of your SquirrelMail install. 
+ * @ignore
+ */
+define('SM_PATH','../');
+
+/**
+ * include SquirrelMail string functions
+ * script needs OneTimePadDecrypt() (functions/strings.php)
+ * and sqgetGlobalVar() (functions/global.php, loaded by strings.php)
+ */
+include_once(SM_PATH.'functions/strings.php');
+
+/**
+ * converts hex string to ip address
+ * @param string $hex hexadecimal string created with squirrelmail ip2hex 
+ *  function in delivery class.
+ * @return string ip address
+ * @since 1.5.1
+ */
+function hex2ip($hex) {
+    if (strlen($hex)==8) {
+        $ret=hexdec(substr($hex,0,2)).'.'
+            .hexdec(substr($hex,2,2)).'.'
+            .hexdec(substr($hex,4,2)).'.'
+            .hexdec(substr($hex,6,2));
+    } elseif (strlen($hex)==32) {
+        $ret=hexdec(substr($hex,0,4)).':'
+            .hexdec(substr($hex,4,4)).':'
+            .hexdec(substr($hex,8,4)).':'
+            .hexdec(substr($hex,12,4)).':'
+            .hexdec(substr($hex,16,4)).':'
+            .hexdec(substr($hex,20,4)).':'
+            .hexdec(substr($hex,24,4)).':'
+            .hexdec(substr($hex,28,4));
+    } else {
+        $ret=$hex;
+    }
+    return $ret;
+}
+
+/** create page headers */
+header('Content-Type: text/html');
+
+echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'
+    ."\n<head>\n<meta name=\"robots\" content=\"noindex,nofollow\">\n"
+    ."</head><body>";
+
+if (sqgetGlobalVar('submit',$submit,SQ_POST)) {
+    if (! sqgetGlobalVar('secret',$secret,SQ_POST) ||
+        empty($secret))
+        echo "<p>You must enter encryption key.</p>\n";
+    if (! sqgetGlobalVar('enc_string',$enc_string,SQ_POST) ||
+        empty($enc_string))
+        echo "<p>You must enter encrypted string.</p>\n";
+
+    if (isset($enc_string) && ! base64_decode($enc_string)) {
+        echo "<p>Encrypted string should be BASE64 encoded.<br />\n"
+            ."Please enter all characters that are listed after header name.</p>\n";
+    } elseif (isset($secret)) {
+        $string=OneTimePadDecrypt($enc_string,base64_encode($secret));
+
+        if (sqgetGlobalVar('ip_addr',$is_addr,SQ_POST)) {
+            $string=hex2ip($string);
+        }
+        echo "<p>Decoded string: ".$string."</p>\n";
+    }
+    echo "<hr />";
+}
+?>
+<form action="<?php echo $PHP_SELF ?>" method="post" >
+<p>
+Secret key: <input type="password" name="secret"><br />
+Encrypted string: <input type="text" name="enc_string"><br />
+Check, if it is an address string: <input type="checkbox" name="ip_addr" /><br />
+<button type="submit" name="submit" value="submit">Submit</button>
+</p>
+</form>
+</body></html>

+ 20 - 0
functions/strings.php

@@ -588,6 +588,16 @@ function get_location () {
  */
 function OneTimePadEncrypt ($string, $epad) {
     $pad = base64_decode($epad);
+
+    if (strlen($pad)>0) {
+        // make sure that pad is longer than string
+        while (strlen($string)>strlen($pad)) {
+            $pad.=$pad;
+        }
+    } else {
+        // FIXME: what should we do when $epad is not base64 encoded or empty.
+    }
+
     $encrypted = '';
     for ($i = 0; $i < strlen ($string); $i++) {
         $encrypted .= chr (ord($string[$i]) ^ ord($pad[$i]));
@@ -608,6 +618,16 @@ function OneTimePadEncrypt ($string, $epad) {
  */
 function OneTimePadDecrypt ($string, $epad) {
     $pad = base64_decode($epad);
+
+    if (strlen($pad)>0) {
+        // make sure that pad is longer than string
+        while (strlen($string)>strlen($pad)) {
+            $pad.=$pad;
+        }
+    } else {
+        // FIXME: what should we do when $epad is not base64 encoded or empty.
+    }
+
     $encrypted = base64_decode ($string);
     $decrypted = '';
     for ($i = 0; $i < strlen ($encrypted); $i++) {