Added IMAP and SMTP STARTTLS extension support.
authortokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Sat, 28 Jan 2006 19:24:08 +0000 (19:24 +0000)
committertokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Sat, 28 Jan 2006 19:24:08 +0000 (19:24 +0000)
Saved SMTP EHLO response in class parameters.
Moved sanitizing of SMTP errors from delivery class to display scripts.
Allowed to use configtest.php when client_ip matches server_ip.
There is no 1.3.3 version. TLS was introduced in 1.4.0.

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

class/deliver/Deliver_SMTP.class.php
config/conf.pl
config/config_default.php
doc/authentication.txt
functions/imap_general.php
plugins/administrator/defines.php
src/compose.php
src/configtest.php
src/read_body.php

index 0fe1a40..6461d77 100644 (file)
@@ -3,7 +3,7 @@
 /**
  * Deliver_SMTP.class.php
  *
- * Delivery backend for the Deliver class.
+ * SMTP delivery backend for the Deliver class.
  *
  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @package squirrelmail
  */
 
+/** @ignore */
+if (!defined('SM_PATH')) define('SM_PATH','../../');
+
 /** This of course depends upon Deliver */
-require_once(SM_PATH . 'class/deliver/Deliver.class.php');
+include_once(SM_PATH . 'class/deliver/Deliver.class.php');
 
 /**
  * Deliver messages using SMTP
  * @package squirrelmail
  */
 class Deliver_SMTP extends Deliver {
+    /**
+     * Array keys are uppercased ehlo keywords
+     * array key values are ehlo params. If ehlo-param contains space, it is splitted into array. 
+     * @var array ehlo
+     * @since 1.5.1
+     */
+    var $ehlo = array();
+    /**
+     * @var string domain
+     * @since 1.5.1
+     */
+    var $domain = '';
+    /**
+     * SMTP STARTTLS rfc: "Both the client and the server MUST know if there 
+     * is a TLS session active."
+     * Variable should be set to true, when encryption is turned on.
+     * @var boolean
+     * @since 1.5.1 
+     */
+    var $tls_enabled = false;
+    /**
+     * Private var
+     * var stream $stream
+     * @todo don't pass stream resource in class method arguments.
+     */
+    //var $stream = false;
+    /** @var string delivery error message */
+    var $dlv_msg = '';
+    /** @var integer delivery error number from server */
+    var $dlv_ret_nr = '';
+    /** @var string delivery error message from server */
+    var $dlv_server_msg = '';
 
     function preWriteToStream(&$s) {
         if ($s) {
@@ -51,18 +86,32 @@ class Deliver_SMTP extends Deliver {
             $from->mailbox = '';
         }
 
-        if (($use_smtp_tls == true) and (check_php_version(4,3)) and (extension_loaded('openssl'))) {
-            $stream = @fsockopen('tls://' . $host, $port, $errorNumber, $errorString);
+        if ($use_smtp_tls == 1) {
+            if ((check_php_version(4,3)) && (extension_loaded('openssl'))) {
+                $stream = @fsockopen('tls://' . $host, $port, $errorNumber, $errorString);
+                $this->tls_enabled = true;
+            } else {
+                /**
+                 * don't connect to server when user asks for smtps and 
+                 * PHP does not support it.
+                 */
+                $errorNumber = '';
+                $errorString = _("Secure SMTP (TLS) is enabled in SquirrelMail configuration, but used PHP version does not support it.");
+            }
         } else {
             $stream = @fsockopen($host, $port, $errorNumber, $errorString);
         }
 
         if (!$stream) {
+            // reset tls state var to default value, if connection fails
+            $this->tls_enabled = false;
+            // set error messages
             $this->dlv_msg = $errorString;
             $this->dlv_ret_nr = $errorNumber;
             $this->dlv_server_msg = _("Can't open SMTP stream.");
             return(0);
         }
+        // get server greeting
         $tmp = fgets($stream, 1024);
         if ($this->errorCheck($tmp, $stream)) {
             return(0);
@@ -84,7 +133,8 @@ class Deliver_SMTP extends Deliver {
 
         /* Lets introduce ourselves */
         fputs($stream, "EHLO $helohost\r\n");
-        $tmp = fgets($stream,1024);
+        // Read ehlo response
+        $tmp = $this->parse_ehlo_response($stream);
         if ($this->errorCheck($tmp,$stream)) {
             // fall back to HELO if EHLO is not supported
             if ($this->dlv_ret_nr == '500') {
@@ -98,6 +148,59 @@ class Deliver_SMTP extends Deliver {
             }
         }
 
+        /**
+         * Implementing SMTP STARTTLS (rfc2487) in php 5.1.0+
+         * http://www.php.net/stream-socket-enable-crypto
+         */
+        if ($use_smtp_tls == 2) {
+            if (function_exists('stream_socket_enable_crypto')) {
+                // don't try starting tls, when client thinks that it is already active
+                if ($this->tls_enabled) {
+                    $this->dlv_msg = _("TLS session is already activated.");
+                    return 0;
+                } elseif (!array_key_exists('STARTTLS',$this->ehlo)) {
+                    // check for starttls in ehlo response
+                    $this->dlv_msg = _("SMTP STARTTLS is enabled in SquirrelMail configuration, but used SMTP server does not support it");
+                    return 0;
+                }
+
+                // issue starttls command
+                fputs($stream, "STARTTLS\r\n");
+                // get response
+                $tmp = fgets($stream,1024);
+                if ($this->errorCheck($tmp,$stream)) {
+                    return 0;
+                }
+
+                // start crypto on connection. suppress function errors.
+                if (@stream_socket_enable_crypto($stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+                    // starttls was successful (rfc2487 5.2 Result of the STARTTLS Command)
+                    // get new EHLO response
+                    fputs($stream, "EHLO $helohost\r\n");
+                    // Read ehlo response
+                    $tmp = $this->parse_ehlo_response($stream);
+                    if ($this->errorCheck($tmp,$stream)) {
+                        // don't revert to helo here. server must support ESMTP
+                        return 0;
+                    }
+                    // set information about started tls
+                    $this->tls_enabled = true;
+                } else {
+                    /**
+                     * stream_socket_enable_crypto() call failed.
+                     */
+                    $this->dlv_msg = _("Unable to start TLS.");
+                    return 0;
+                    // Bug: can't get error message. See comments in sqimap_create_stream().
+                }
+            } else {
+                // php install does not support stream_socket_enable_crypto() function
+                $this->dlv_msg = _("SMTP STARTTLS is enabled in SquirrelMail configuration, but used PHP version does not support functions that allow to enable encryption on open socket.");
+                return 0;
+            }
+        }
+
+        // FIXME: check ehlo response before using authentication
         if (( $smtp_auth_mech == 'cram-md5') or ( $smtp_auth_mech == 'digest-md5' )) {
             // Doing some form of non-plain auth
             if ($smtp_auth_mech == 'cram-md5') {
@@ -311,7 +414,7 @@ class Deliver_SMTP extends Deliver {
         }
 
         $this->dlv_msg = $message;
-        $this->dlv_server_msg = nl2br(htmlspecialchars($server_msg));
+        $this->dlv_server_msg = $server_msg;
 
         return true;
     }
@@ -346,6 +449,66 @@ class Deliver_SMTP extends Deliver {
             fclose($popConnection);
         }
     }
+
+    /**
+     * Parses ESMTP EHLO response (rfc1869)
+     *
+     * Reads SMTP response to EHLO command and fills class variables 
+     * (ehlo array and domain string). Returns last line.
+     * @param stream $stream smtp connection stream.
+     * @return string last ehlo line
+     * @since 1.5.1
+     */
+    function parse_ehlo_response($stream) {
+        // don't cache ehlo information
+        $this->ehlo=array();
+        $ret = '';
+        $firstline = true;
+        /**
+         * ehlo mailclient.example.org
+         * 250-mail.example.org
+         * 250-PIPELINING
+         * 250-SIZE 52428800
+         * 250-DATAZ
+         * 250-STARTTLS
+         * 250-AUTH LOGIN PLAIN
+         * 250 8BITMIME
+         */
+        while ($line=fgets($stream, 1024)){
+            // match[1] = first symbol after 250
+            // match[2] = domain or ehlo-keyword
+            // match[3] = greeting or ehlo-param
+            // match space after keyword in ehlo-keyword CR LF
+            if (preg_match("/^250(-|\s)(\S*)\s+(\S.*)\r\n/",$line,$match)||
+                preg_match("/^250(-|\s)(\S*)\s*\r\n/",$line,$match)) {
+                if ($firstline) {
+                    // first ehlo line (250[-\ ]domain SP greeting)
+                    $this->domain = $match[2];
+                    $firstline=false;
+                } elseif (!isset($match[3])) {
+                    // simple one word extension
+                    $this->ehlo[strtoupper($match[2])]='';
+                } elseif (!preg_match("/\s/",trim($match[3]))) {
+                    // extension with one option
+                    // yes, I know about ctype extension. no, i don't want to depend on it
+                    $this->ehlo[strtoupper($match[2])]=trim($match[3]);
+                } else {
+                    // ehlo-param with spaces
+                    $this->ehlo[strtoupper($match[2])]=explode(' ',trim($match[3]));
+                }
+                if ($match[1]==' ') {
+                    // stop while cycle, if we reach last 250 line
+                    $ret = $line;
+                    break;
+                }
+            } else {
+                // this is not 250 response
+                $ret = $line;
+                break;
+            }
+        }
+        return $ret;
+    }
 }
 
 ?>
\ No newline at end of file
index cbd20c9..d42be3d 100755 (executable)
@@ -321,6 +321,7 @@ while ( $line = <FILE> ) {
 }
 close FILE;
 
+# FIXME: unknown introduction date
 $useSendmail = 'false'                  if ( lc($useSendmail) ne 'true' );
 $sendmail_path = "/usr/sbin/sendmail"   if ( !$sendmail_path );
 $pop_before_smtp = 'false'              if ( !$pop_before_smtp );
@@ -349,13 +350,15 @@ $allow_advanced_search = 0              if ( !$allow_advanced_search) ;
 $prefs_user_field = 'user'              if ( !$prefs_user_field );
 $prefs_key_field = 'prefkey'            if ( !$prefs_key_field );
 $prefs_val_field = 'prefval'            if ( !$prefs_val_field );
+$session_name = 'SQMSESSID'             if ( !$session_name );
+$skip_SM_header = 'false'               if ( !$skip_SM_header );
+$default_use_javascript_addr_book = 'false' if (! $default_use_javascript_addr_book);
+
+# since 1.4.0
 $use_smtp_tls= 'false'                  if ( !$use_smtp_tls);
 $smtp_auth_mech = 'none'                if ( !$smtp_auth_mech );
 $use_imap_tls = 'false'                 if ( !$use_imap_tls );
 $imap_auth_mech = 'login'               if ( !$imap_auth_mech );
-$session_name = 'SQMSESSID'             if ( !$session_name );
-$skip_SM_header = 'false'               if ( !$skip_SM_header );
-$default_use_javascript_addr_book = 'false' if (! $default_use_javascript_addr_book);
 
 # since 1.5.0
 $show_alternative_names = 'false'       if ( !$show_alternative_names );
@@ -402,6 +405,11 @@ if ( !%fontsets) {
                  'verasans',  'bitstream vera sans,verdana,sans-serif');
 }
 
+# $use_imap_tls and $use_smtp_tls are switched to integer since 1.5.1
+$use_imap_tls = 0                      if ( $use_imap_tls eq 'false');
+$use_imap_tls = 1                      if ( $use_imap_tls eq 'true');
+$use_smtp_tls = 0                      if ( $use_smtp_tls eq 'false');
+$use_smtp_tls = 1                      if ( $use_smtp_tls eq 'true');
 
 if ( $ARGV[0] eq '--install-plugin' ) {
     print "Activating plugin " . $ARGV[1] . "\n";
@@ -760,7 +768,7 @@ while ( ( $command ne "q" ) && ( $command ne "Q" ) && ( $command ne ":q" ) ) {
               if    ( $command == 4 )  { $imapServerAddress      = command12(); }
               elsif ( $command == 5 )  { $imapPort               = command13(); }
               elsif ( $command == 6 )  { $imap_auth_mech     = command112a(); }
-              elsif ( $command == 7 )  { $use_imap_tls       = command113("IMAP",$use_imap_tls); }
+              elsif ( $command == 7 )  { $use_imap_tls       = command_use_tls("IMAP",$use_imap_tls); }
               elsif ( $command == 8 )  { $imap_server_type       = command19(); }
               elsif ( $command == 9 )  { $optional_delimiter     = command111(); }
             } elsif ( $show_smtp_settings && lc($useSendmail) eq 'true' ) {
@@ -772,7 +780,7 @@ while ( ( $command ne "q" ) && ( $command ne "Q" ) && ( $command ne ":q" ) ) {
               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 == 8 )  { $use_smtp_tls      = command_use_tls("SMTP",$use_smtp_tls); }
               elsif ( $command == 9 )  { $encode_header_key      = command114(); }
             }
         } elsif ( $menu == 3 ) {
@@ -1430,27 +1438,31 @@ sub command112b {
 # TLS
 # This sub is reused for IMAP and SMTP
 # Args: service name, default value
-sub command113 {
+sub command_use_tls {
     my($default_val,$service,$inval);
     $service=$_[0];
     $default_val=$_[1];
     print "TLS (Transport Layer Security) encrypts the traffic between server and client.\n";
-    print "If you're familiar with SSL, you get the idea.\n";
-    print "To use this feature, your " . $service . " server must offer TLS\n";
-    print "capability, plus PHP 4.3.x with OpenSSL support.\n";
-    print "\nIf your " . $service . " server is localhost, you can safely disable this.\n";
+    print "STARTTLS extensions allow to start encryption on existing plain text connection.\n";
+    print "These options add specific PHP and IMAP server configuration requirements.\n";
+    print "See SquirrelMail documentation about connection security.\n";
+    print "\n";
+    print "If your " . $service . " server is localhost, you can safely disable this.\n";
     print "If it is remote, you may wish to seriously consider enabling this.\n";
-    print "Enable TLS (y/n) [$WHT";
-    if ($default_val eq 'true') {
-      print "y";
-    } else {
-      print "n";
+    $valid_input=0;
+    while ($valid_input eq 0) {
+        print "\nSelect connection security model:\n";
+        print " 0 - Use plain text connection\n";
+        print " 1 - Use TLS connection\n";
+        print " 2 - Use STARTTLS extension\n";
+        print "Select [$default_val]: ";
+        $inval=<STDIN>;
+        $inval=trim($inval);
+        if ($inval =~ /^[012]$/ || $inval eq '') {
+            $valid_input = 1;
+        }
     }
-    print "$NRM]: $WHT";
-    $inval=<STDIN>;
-    $inval =~ tr/yn//cd;
-    return 'true'  if ( $inval eq "y" );
-    return 'false' if ( $inval eq "n" );
+    if ($inval ne '') {$default_val = $inval};
     return $default_val;
 }
 
index 0759d63..98107b9 100644 (file)
@@ -229,22 +229,30 @@ $imapPort = 143;
 $imap_server_type = 'other';
 
 /**
- * Advanced IMAP authentication options control
+ * Secure IMAP connection controls
  *
- * CRAM-MD5, DIGEST-MD5, Plain, and TLS
- * Set reasonable defaults - you'd never know this was there unless you ask for it
- * @global bool $use_imap_tls
+ * 0 - use plain text connection,
+ * 1 - use imaps (adds tls:// prefix to hostname),
+ * 2 - use IMAP STARTTLS extension (rfc2595).
+ *
+ * Was boolean before 1.5.1.
+ * @global integer $use_imap_tls
+ * @since 1.4.0
  */
-$use_imap_tls = false;
+$use_imap_tls = 0;
 
 /**
- * Advanced SMTP authentication options control
+ * Secure SMTP connection controls
+ *
+ * 0 - use plain text connection,
+ * 1 - use ssmtp (adds tls:// prefix to hostname),
+ * 2 - use SMTP STARTTLS extension (rfc2487).
  *
- * CRAM-MD5, DIGEST-MD5, Plain, and TLS
- * Set reasonable defaults - you'd never know this was there unless you ask for it
- * @global bool $use_smtp_tls
+ * Was boolean before 1.5.1.
+ * @global integer $use_smtp_tls
+ * @since 1.4.0
  */
-$use_smtp_tls = false;
+$use_smtp_tls = 0;
 
 /**
  * SMTP authentication mechanism
index 98e6110..ae78bf4 100644 (file)
@@ -4,8 +4,8 @@ $Id$
 Chris Hilts tassium@squirrelmail.org
 **********************************************
 
-Prior to SquirrelMail 1.3.3, only plaintext logins for IMAP and SMTP were
-supported.  With the release of SquirrelMail 1.3.3, support for the
+Prior to SquirrelMail 1.4.0, only plaintext logins for IMAP and SMTP were
+supported.  With the release of SquirrelMail 1.4.0, support for the
 CRAM-MD5 and DIGEST-MD5 auth mechanisms has been added.  TLS support has
 also been added.  It is possible to use different methods for both IMAP and
 SMTP. TLS is able to be enabled on a per-service basis as well.
@@ -21,20 +21,26 @@ REQUIREMENTS
 ------------
 
 CRAM/DIGEST-MD5
-* SquirrelMail 1.3.3 or higher
+* SquirrelMail 1.4.0 or higher
 * If you have the mhash extension to PHP, it will automatically
   be used, which may help performance on heavily loaded servers.
   ** NOTE: mhash is optional and no longer a requirement **
+* Digest-MD5 authentication needs PHP XML extension.
 
 TLS
-* SquirrelMail 1.3.3 or higher
+* SquirrelMail 1.4.0 or higher
 * PHP 4.3.0 or higher (Check Release Notes for PHP 4.3.x information)
-* The "STARTTLS" command is NOT supported.  The server you wish to use TLS
-  on must have a dedicated port listening for TLS connections. (ie. port
-  993 for IMAP, 465 for SMTP)
+* The server you wish to use TLS on must have a dedicated port listening for 
+  TLS connections. (ie. port 993 for IMAP, 465 for SMTP). See STARTTLS 
+  requirements, if you want to use IMAP or SMTP STARTTLS extension.
 * If you use PHP 4.3.x, OpenSSL support must be compiled staticly. See
   PHP bug #29934 (http://bugs.php.net/bug.php?id=29934)
 
+STARTTLS
+* SquirrelMail 1.5.1 or higher
+* PHP 5.1.0rc1 or higher (stream_socket_enable_crypto() function)
+* Server with STARTTLS extension support
+
 CONFIGURATION
 -------------
 
@@ -44,9 +50,10 @@ conf.pl can now attempt to detect which mechanisms your servers support.
 You must have set the host and port before attempting to detect, or you
 may get inaccurate results, or a long wait while the connection times out.
 
-If you get results that you know are wrong when you use auto-detection, I
-need to know about it. Please send me the results you got, the results you
-expected, and server type, name, and version (eg. "imap, Cyrus, v2.1.9").
+If you get results that you know are wrong when you use auto-detection, send
+information about it to SquirrelMail developers. Provide the results you got, 
+the results you expected, and server type, name, and version (eg. "imap, 
+Cyrus, v2.1.9").
 
 KNOWN ISSUES
 ------------
@@ -104,5 +111,30 @@ These values will be used to connect to the SMTP server as long
 as the authentication mechanism is something besides 'none', i.e.
 'login','plain','cram-md5', or 'digest-md5'.
 
+DEBUGGING SSL ERROR MESSAGES
+----------------------------
+
+SquirrelMail disables display of PHP errors in fsockopen() and 
+stream_socket_enable_crypto() function calls. These functions use PHP error
+handler to display connection errors and SquirrelMail tries to handle
+errors without displaying debugging information to end user. If you use TLS or
+STARTTLS and get connection errors, try reproducing them in configtest.php
+script or remove @ symbol from fsockopen() and stream_socket_enable_crypto()
+calls in SquirrelMail scripts.
+
+Possible error messages:
+* SSL: Connection reset by peer in some script.
+  Error happened in IMAP server and server dropped connection. It is possible
+  that error is logged in system or imap logs.
+
+* SSL operation failed with code 1. OpenSSL Error messages: error:14094410:SSL
+routines:func(148):reason(1040) in some script.
+  Error generated by SSL libraries. Locate numbers listed 'SSL
+  routines:func(###):reason(####)' string, find ssl.h file in your OpenSSL
+  sources and locate same numbers listed in '/* Error codes for the SSL
+  functions. */' section. Error define can be self explanatory. If you don't
+  understand it, search for error or that define in your favorite search
+  engine.
+
 
 [End]
index 6ba8df3..4a85407 100755 (executable)
@@ -625,17 +625,20 @@ function sqimap_read_data ($imap_stream, $tag_uid, $handle_errors,
 
 /**
  * Connects to the IMAP server and returns a resource identifier for use with
- * the other SquirrelMail IMAP functions.  Does NOT login!
+ * the other SquirrelMail IMAP functions. Does NOT login!
  * @param string server hostname of IMAP server
  * @param int port port number to connect to
- * @param bool tls whether to use TLS when connecting.
+ * @param integer $tls whether to use plain text(0), TLS(1) or STARTTLS(2) when connecting.
+ *  Argument was boolean before 1.5.1.
  * @return imap-stream resource identifier
  * @since 1.5.0 (usable only in 1.5.1 or later)
  */
-function sqimap_create_stream($server,$port,$tls=false) {
+function sqimap_create_stream($server,$port,$tls=0) {
     global $squirrelmail_language;
 
-    if ($tls == true) {
+    // FIXME: ipv6 address support
+
+    if ($tls == 1) {
         if ((check_php_version(4,3)) and (extension_loaded('openssl'))) {
             /* Use TLS by prefixing "tls://" to the hostname */
             $server = 'tls://' . $server;
@@ -662,6 +665,73 @@ function sqimap_create_stream($server,$port,$tls=false) {
         exit;
     }
     $server_info = fgets ($imap_stream, 1024);
+
+    /**
+     * Implementing IMAP STARTTLS (rfc2595) in php 5.1.0+
+     * http://www.php.net/stream-socket-enable-crypto
+     */
+    if ($tls == 2) {
+        if (function_exists('stream_socket_enable_crypto')) {
+            // check starttls capability, don't use cached capability version
+            if (! sqimap_capability($imap_stream, 'STARTTLS', false)) {
+                // imap server does not declare starttls support
+                sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
+                                 '','',
+                                 _("IMAP STARTTLS is enabled in SquirrelMail configuration, but used IMAP server does not support STARTTLS."));
+                exit;
+            }
+
+            // issue starttls command and check response
+            sqimap_run_command($imap_stream, 'STARTTLS', false, $starttls_response, $starttls_message);
+            // check response
+            if ($starttls_response!='OK') {
+                // starttls command failed
+                sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
+                                 'STARTTLS',
+                                 _("Server replied: "),
+                                 $starttls_message);
+                exit();
+            }
+
+            // start crypto on connection. suppress function errors.
+            if (@stream_socket_enable_crypto($imap_stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+                // starttls was successful
+
+                /**
+                 * RFC 2595 requires to discard CAPABILITY information after successful 
+                 * STARTTLS command. We don't follow RFC, because SquirrelMail stores CAPABILITY 
+                 * information only after successful login (src/redirect.php) and cached information 
+                 * is used only in other php script connections after successful STARTTLS. If script 
+                 * issues sqimap_capability() call before sqimap_login() and wants to get initial 
+                 * capability response, script should set third sqimap_capability() argument to false. 
+                 */
+                //sqsession_unregister('sqimap_capabilities');            
+            } else {
+                /**
+                 * stream_socket_enable_crypto() call failed. Possible issues:
+                 * - broken ssl certificate (uw drops connection, error is in syslog mail facility)
+                 * - some ssl error (can reproduce with STREAM_CRYPTO_METHOD_SSLv3_CLIENT, PHP E_WARNING 
+                 *   suppressed in stream_socket_enable_crypto() call)
+                 */
+                sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
+                                 '','',
+                                 _("Unable to start TLS."));
+                /**
+                 * Bug: stream_socket_enable_crypto() does not register SSL errors in 
+                 * openssl_error_string() or stream notification wrapper and displays 
+                 * them in E_WARNING level message. It is impossible to retrieve error 
+                 * message without own error handler.
+                 */
+                exit;
+            }
+        } else {
+            // php install does not support stream_socket_enable_crypto() function
+            sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
+                             '','',
+                             _("IMAP STARTTLS is enabled in SquirrelMail configuration, but used PHP version does not support functions that allow to enable encryption on open socket."));
+            exit;
+        }
+    }
     return $imap_stream;
 }
 
@@ -858,15 +928,16 @@ function sqimap_logout ($imap_stream) {
  * If capability is set, returns only that specific capability,
  * else returns array of all capabilities.
  * @param stream $imap_stream
- * @param string $capability (optional since 1.3.0)
+ * @param string $capability (since 1.3.0)
+ * @param boolean $bUseCache (since 1.5.1) Controls use of capability data stored in session 
  * @return mixed (string if $capability is set and found,
  *  false, if $capability is set and not found,
  *  array if $capability not set)
  */
-function sqimap_capability($imap_stream, $capability='') {
-    sqgetGlobalVar('sqimap_capabilities', $sqimap_capabilities, SQ_SESSION);
+function sqimap_capability($imap_stream, $capability='', $bUseCache=true) {
+    // sqgetGlobalVar('sqimap_capabilities', $sqimap_capabilities, SQ_SESSION);
 
-    if (!is_array($sqimap_capabilities)) {
+    if (!$bUseCache || ! sqgetGlobalVar('sqimap_capabilities', $sqimap_capabilities, SQ_SESSION)) {
         $read = sqimap_run_command($imap_stream, 'CAPABILITY', true, $a, $b);
 
         $c = explode(' ', $read[0]);
index 0f496e4..6e5e126 100644 (file)
@@ -121,10 +121,13 @@ $defcfg = array( '$config_version' => array( 'name' => _("Config File Version"),
                                                  'comment' => _("Use &quot;detect&quot; to auto-detect."),
                                                  'size' => 10,
                                                  'default' => 'detect' ),
-                 '$use_imap_tls' => array( 'name' => _("Use TLS for IMAP Connections"),
-                                           'type' => SMOPT_TYPE_BOOLEAN,
-                                           'comment' => _("Requires PHP 4.3.x! Experimental."),
-                                           'default' => false ),
+                 '$use_imap_tls' => array( 'name' => _("IMAP Connection Security"),
+                                           'type' => SMOPT_TYPE_STRLIST,
+                                           'posvals' => array( 0 => _("Plain text connection"),
+                                                               1 => _("Secure IMAP (TLS) connection"),
+                                                               2 => _("IMAP STARTTLS connection")),
+                                           'comment' => _("Requires higher PHP version and special functions. See SquirrelMail documentation."),
+                                           'default' => 0 ),
                  '$imap_auth_mech' => array( 'name' => _("IMAP Authentication Type"),
                                              'type' => SMOPT_TYPE_STRLIST,
                                              'posvals' => array('login' => _("IMAP login"),
@@ -145,10 +148,13 @@ $defcfg = array( '$config_version' => array( 'name' => _("Config File Version"),
                                                 'size' => 40 ),
                  '$smtpPort' => array( 'name' => _("SMTP Server Port"),
                                        'type' => SMOPT_TYPE_INTEGER ),
-                 '$use_smtp_tls' => array( 'name' => _("Use TLS for SMTP Connections"),
-                                           'type' => SMOPT_TYPE_BOOLEAN,
-                                           'comment' => _("Requires PHP 4.3.x! Experimental."),
-                                           'default' => false ),
+                 '$use_smtp_tls' => array( 'name' => _("SMTP Connection Security"),
+                                           'type' => SMOPT_TYPE_STRLIST,
+                                           'posvals' => array( 0 => _("Plain text connection"),
+                                                               1 => _("Secure IMAP (TLS) connection"),
+                                                               2 => _("IMAP STARTTLS connection")),
+                                           'comment' => _("Requires higher PHP version and special functions. See SquirrelMail documentation."),
+                                           'default' => 0 ),
                  '$smtp_auth_mech' => array( 'name' => _("SMTP Authentication Type"),
                                              'type' => SMOPT_TYPE_STRLIST,
                                              'posvals' => array('none' => _("No SMTP auth"),
index 4f610ca..52f88ad 100644 (file)
@@ -1640,9 +1640,14 @@ function deliverMessage($composeMessage, $draft=false) {
     }
     if (!$success) {
         // $deliver->dlv_server_msg is not always server's reply
-        $msg  = $deliver->dlv_msg . '<br />' .
-            _("Server replied:") . ' ' . $deliver->dlv_ret_nr . ' ' .
-            $deliver->dlv_server_msg;
+        $msg  = $deliver->dlv_msg;
+        if (!empty($deliver->dlv_server_msg)) {
+            // add 'server replied' part only when it is not empty.
+            // Delivery error can be generated by delivery class itself
+            $msg.='<br />' .
+                _("Server replied:") . ' ' . $deliver->dlv_ret_nr . ' ' .
+                nl2br(htmlspecialchars($deliver->dlv_server_msg));
+        }
         plain_error_message($msg, $color);
     } else {
         unset ($deliver);
index af653b4..d0080d5 100644 (file)
@@ -77,7 +77,10 @@ if(!in_array('strings.php', $included)) {
 /* Block remote use of script */
 if (! $allow_remote_configtest) {
     sqGetGlobalVar('REMOTE_ADDR',$client_ip,SQ_SERVER);
-    if (! isset($client_ip) || $client_ip!='127.0.0.1') {
+    sqGetGlobalVar('SERVER_ADDR',$server_ip,SQ_SERVER);
+
+    if ((! isset($client_ip) || $client_ip!='127.0.0.1') &&
+        (! isset($client_ip) || ! isset($server_ip) || $client_ip!=$server_ip)) {
         do_err('Enable "Allow remote configtest" option in squirrelmail configuration in order to use this script.');
     }
 }
@@ -89,6 +92,8 @@ echo "<p><table>\n<tr><td>SquirrelMail version:</td><td><b>" . $version . "</b><
          date ('d F Y H:i:s', filemtime(SM_PATH . 'config/config.php')) .
          "</b></td></tr>\n</table>\n</p>\n\n";
 
+/* TODO: check $config_version here */
+
 echo "Checking PHP configuration...<br />\n";
 
 if(!check_php_version(4,1,0)) {
@@ -222,10 +227,10 @@ if ( $squirrelmail_default_language != 'en_US' ) {
 
 echo $IND . "Base URL detected as: <tt>" . htmlspecialchars(get_location()) . "</tt><br />\n";
 
+/* check minimal requirements for other security options */
 
-/* check outgoing mail */
-
-if($use_smtp_tls || $use_imap_tls) {
+/* imaps or ssmtp */
+if($use_smtp_tls == 1 || $use_imap_tls == 1) {
     if(!check_php_version(4,3,0)) {
         do_err('You need at least PHP 4.3.0 for SMTP/IMAP TLS!');
     }
@@ -233,6 +238,20 @@ if($use_smtp_tls || $use_imap_tls) {
         do_err('You need the openssl PHP extension to use SMTP/IMAP TLS!');
     }
 }
+/* starttls extensions */
+if($use_smtp_tls == 2 || $use_imap_tls == 2) {
+    if (! function_exists('stream_socket_enable_crypto')) {
+        do_err('If you want to use STARTTLS extension, you need stream_socket_enable_crypto() function from PHP 5.1.0 and newer.');
+    }
+}
+/* digest-md5 */
+if ($smtp_auth_mech=='digest-md5' || $imap_auth_mech =='digest-md5') {
+    if (!extension_loaded('xml')) {
+        do_err('You need the PHP XML extension to use Digest-MD5 authentication!');
+    }
+}
+
+/* check outgoing mail */
 
 echo "Checking outgoing mail service....<br />\n";
 
@@ -247,7 +266,7 @@ if($useSendmail) {
 
     echo $IND . "sendmail OK<br />\n";
 } else {
-    $stream = fsockopen( ($use_smtp_tls?'tls://':'').$smtpServerAddress, $smtpPort,
+    $stream = fsockopen( ($use_smtp_tls==1?'tls://':'').$smtpServerAddress, $smtpPort,
                         $errorNumber, $errorString);
     if(!$stream) {
         do_err("Error connecting to SMTP server \"$smtpServerAddress:$smtpPort\".".
@@ -261,6 +280,56 @@ if($useSendmail) {
         htmlspecialchars($smtpline));
     }
 
+    /* smtp starttls checks */
+    if ($use_smtp_tls==2) {
+        // if something breaks, script should close smtp connection on exit.
+
+        // say helo
+        fwrite($stream,"EHLO $client_ip\r\n");
+
+        $ehlo=array();
+        $ehlo_error = false;
+        while ($line=fgets($stream, 1024)){
+            if (preg_match("/^250(-|\s)(\S*)\s+(\S.*)/",$line,$match)||
+                preg_match("/^250(-|\s)(\S*)\s+/",$line,$match)) {
+                if (!isset($match[3])) {
+                    // simple one word extension
+                    $ehlo[strtoupper($match[2])]='';
+                } else {
+                    // ehlo-keyword + ehlo-param
+                    $ehlo[strtoupper($match[2])]=trim($match[3]);
+                }
+                if ($match[1]==' ') {
+                    $ret = $line;
+                    break;
+                }
+            } else {
+                // 
+                $ehlo_error = true;
+                $ehlo[]=$line;
+                break;
+            }
+        }
+        if ($ehlo_error) {
+            do_err('SMTP EHLO failed. You need ESMTP support for SMTP STARTTLS');
+        } elseif (!array_key_exists('STARTTLS',$ehlo)) {
+            do_err('STARTTLS support is not declared by SMTP server.');
+        }
+
+        fwrite($stream,"STARTTLS\r\n");
+        $starttls_response=fgets($stream, 1024);
+        if ($starttls_response[0]!=2) {
+            $starttls_cmd_err = 'SMTP STARTTLS failed. Server replied: '
+                .htmlspecialchars($starttls_response);
+            do_err($starttls_cmd_err);
+        } elseif(! stream_socket_enable_crypto($stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+            do_err('Failed to enable encryption on SMTP STARTTLS connection.');
+        } else {
+            echo $IND . "SMTP STARTTLS extension looks OK.<br />\n";
+        }
+        // According to RFC we should second ehlo call here.
+    }
+
     fputs($stream, 'QUIT');
     fclose($stream);
     echo $IND . 'SMTP server OK (<tt><small>'.
@@ -291,7 +360,7 @@ if($useSendmail) {
 echo "Checking IMAP service....<br />\n";
 
 /** Can we open a connection? */
-$stream = fsockopen( ($use_imap_tls?'tls://':'').$imapServerAddress, $imapPort,
+$stream = fsockopen( ($use_imap_tls==1?'tls://':'').$imapServerAddress, $imapPort,
                        $errorNumber, $errorString);
 if(!$stream) {
     do_err("Error connecting to IMAP server \"$imapServerAddress:$imapPort\".".
@@ -311,7 +380,43 @@ echo $IND . 'IMAP server ready (<tt><small>'.
 
 /** Check capabilities */
 fputs($stream, "A001 CAPABILITY\r\n");
-$capline = fgets($stream, 1024);
+$capline = '';
+while ($line=fgets($stream, 1024)){
+  if (preg_match("/A001.*/",$line)) {
+     break;
+  } else {
+     $capline.=$line;
+  }
+}
+
+/* don't display capabilities before STARTTLS */
+if ($use_imap_tls==2 && stristr($capline, 'STARTTLS') === false) {
+    do_err('Your server doesn\'t support STARTTLS.');
+} else {
+    /* try starting starttls */
+    fwrite($stream,"A002 STARTTLS\r\n");
+    $starttls_line=fgets($stream, 1024);
+    if (! preg_match("/^A002 OK.*/i",$starttls_line)) {
+        $imap_starttls_err = 'IMAP STARTTLS failed. Server replied: '
+                .htmlspecialchars($starttls_line);
+        do_err($imap_starttls_err);
+    } elseif (! stream_socket_enable_crypto($stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+        do_err('Failed to enable encryption on IMAP connection.');
+    } else {
+        echo $IND . "IMAP STARTTLS extension looks OK.<br />\n";
+    }
+
+    // get new capability line
+    fwrite($stream,"A003 CAPABILITY\r\n");
+    $capline='';
+    while ($line=fgets($stream, 1024)){
+        if (preg_match("/A003.*/",$line)) {
+            break;
+        } else {
+            $capline.=$line;
+        }
+    }
+}
 
 echo $IND . 'Capabilities: <tt>'.htmlspecialchars($capline)."</tt><br />\n";
 
@@ -320,10 +425,9 @@ if($imap_auth_mech == 'login' && stristr($capline, 'LOGINDISABLED') !== FALSE) {
         'Try enabling another authentication mechanism like CRAM-MD5, DIGEST-MD5 or TLS-encryption '.
         'in the SquirrelMail configuration.', FALSE);
 }
-/* don't test for STARTTLS in CAPABILITY */
 
 /** OK, close connection */
-fputs($stream, "A002 LOGOUT\r\n");
+fputs($stream, "A004 LOGOUT\r\n");
 fclose($stream);
 
 echo "Checking internationalization (i18n) settings...<br />\n";
index a4706cf..2e45408 100644 (file)
@@ -263,9 +263,12 @@ function SendMDN ( $mailbox, $passed_id, $sender, $message, $imapConnection) {
         $success = $deliver->finalizeStream($stream);
     }
     if (!$success) {
-        $msg  = $deliver->dlv_msg . '<br />' .
+        $msg = $deliver->dlv_msg;
+        if (! empty($deliver->dlv_server_msg)) {
+            $msg.= '<br />' .
                 _("Server replied:") . ' ' . $deliver->dlv_ret_nr . ' ' .
-                $deliver->dlv_server_msg;
+                nl2br(htmlspecialchars($deliver->dlv_server_msg));
+        }
         require_once(SM_PATH . 'functions/display_messages.php');
         plain_error_message($msg, $color);
     } else {