replacing skip_SM_header with two different options. If admins want to shoot
authortokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Thu, 12 May 2005 08:15:19 +0000 (08:15 +0000)
committertokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Thu, 12 May 2005 08:15:19 +0000 (08:15 +0000)
their own foot, they have to do that themselves and remove headers
completely by modifying SquirrelMail scripts.

git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@9411 7612ce4b-ef26-0410-bec9-ea0150e637f0

ChangeLog
class/deliver/Deliver.class.php
config/conf.pl
config/config_default.php
contrib/decrypt_headers.php [new file with mode: 0644]
functions/strings.php

index 12c1277..42cae76 100644 (file)
--- a/ChangeLog
+++ b/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
 -------------------------------
index c732bea..9cf091a 100644 (file)
@@ -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;
+    }
 }
 ?>
\ No newline at end of file
index efb0407..fb53a0e 100755 (executable)
@@ -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
@@ -3319,6 +3361,8 @@ sub save_data {
     # 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
         print CF "\$allow_server_sort        = $allow_server_sort;\n";
index 8d24c2e..068c567 100644 (file)
@@ -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.
@@ -526,6 +532,21 @@ $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
  *
  * If you want to enable server side thread sorting options
diff --git a/contrib/decrypt_headers.php b/contrib/decrypt_headers.php
new file mode 100644 (file)
index 0000000..1e38107
--- /dev/null
@@ -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>
\ No newline at end of file
index de9ae87..dca58de 100644 (file)
@@ -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++) {