Source for file Deliver_SMTP.class.php

Documentation is available at Deliver_SMTP.class.php

  1. <?php
  2.  
  3. /**
  4.  * Deliver_SMTP.class.php
  5.  *
  6.  * SMTP delivery backend for the Deliver class.
  7.  *
  8.  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  9.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  10.  * @version $Id: Deliver_SMTP.class.php,v 1.37 2006/07/15 12:00:44 tokul Exp $
  11.  * @package squirrelmail
  12.  */
  13.  
  14. /** @ignore */
  15. if (!defined('SM_PATH')) define('SM_PATH','../../');
  16.  
  17. /** This of course depends upon Deliver */
  18. include_once(SM_PATH 'class/deliver/Deliver.class.php');
  19.  
  20. /**
  21.  * Deliver messages using SMTP
  22.  * @package squirrelmail
  23.  */
  24. class Deliver_SMTP extends Deliver {
  25.     /**
  26.      * Array keys are uppercased ehlo keywords
  27.      * array key values are ehlo params. If ehlo-param contains space, it is splitted into array.
  28.      * @var array ehlo
  29.      * @since 1.5.1
  30.      */
  31.     var $ehlo = array();
  32.     /**
  33.      * @var string domain
  34.      * @since 1.5.1
  35.      */
  36.     var $domain = '';
  37.     /**
  38.      * SMTP STARTTLS rfc: "Both the client and the server MUST know if there
  39.      * is a TLS session active."
  40.      * Variable should be set to true, when encryption is turned on.
  41.      * @var boolean 
  42.      * @since 1.5.1
  43.      */
  44.     var $tls_enabled = false;
  45.     /**
  46.      * Private var
  47.      * var stream $stream
  48.      * @todo don't pass stream resource in class method arguments.
  49.      */
  50.     //var $stream = false;
  51.         /** @var string delivery error message */
  52.     var $dlv_msg = '';
  53.     /** @var integer delivery error number from server */
  54.     var $dlv_ret_nr = '';
  55.     /** @var string delivery error message from server */
  56.     var $dlv_server_msg = '';
  57.  
  58.     function preWriteToStream(&$s{
  59.         if ($s{
  60.             if ($s{0== '.')   $s '.' $s;
  61.             $s str_replace("\n.","\n..",$s);
  62.         }
  63.     }
  64.  
  65.     function initStream($message$domain$length=0$host=''$port=''$user=''$pass=''$authpop=false{
  66.         global $use_smtp_tls,$smtp_auth_mech;
  67.  
  68.         if ($authpop{
  69.             $this->authPop($host''$user$pass);
  70.         }
  71.  
  72.         $rfc822_header $message->rfc822_header;
  73.  
  74.         $from $rfc822_header->from[0];
  75.         $to =   $rfc822_header->to;
  76.         $cc =   $rfc822_header->cc;
  77.         $bcc =  $rfc822_header->bcc;
  78.         $content_type  $rfc822_header->content_type;
  79.  
  80.         // MAIL FROM: <from address> MUST be empty in cae of MDN (RFC2298)
  81.         if ($content_type->type0 == 'multipart' &&
  82.             $content_type->type1 == 'report' &&
  83.             isset($content_type->properties['report-type']&&
  84.             $content_type->properties['report-type']=='disposition-notification'{
  85.             // reinitialize the from object because otherwise the from header somehow
  86.             // is affected. This $from var is used for smtp command MAIL FROM which
  87.             // is not the same as what we put in the rfc822 header.
  88.             $from new AddressStructure();
  89.             $from->host '';
  90.             $from->mailbox '';
  91.         }
  92.  
  93.         if ($use_smtp_tls == 1{
  94.             if ((check_php_version(4,3)) && (extension_loaded('openssl'))) {
  95.                 $stream @fsockopen('tls://' $host$port$errorNumber$errorString);
  96.                 $this->tls_enabled = true;
  97.             else {
  98.                 /**
  99.                  * don't connect to server when user asks for smtps and 
  100.                  * PHP does not support it.
  101.                  */
  102.                 $errorNumber '';
  103.                 $errorString _("Secure SMTP (TLS) is enabled in SquirrelMail configuration, but used PHP version does not support it.");
  104.             }
  105.         else {
  106.             $stream @fsockopen($host$port$errorNumber$errorString);
  107.         }
  108.  
  109.         if (!$stream{
  110.             // reset tls state var to default value, if connection fails
  111.             $this->tls_enabled = false;
  112.             // set error messages
  113.             $this->dlv_msg = $errorString;
  114.             $this->dlv_ret_nr = $errorNumber;
  115.             $this->dlv_server_msg = _("Can't open SMTP stream.");
  116.             return(0);
  117.         }
  118.         // get server greeting
  119.         $tmp fgets($stream1024);
  120.         if ($this->errorCheck($tmp$stream)) {
  121.             return(0);
  122.         }
  123.  
  124.         /*
  125.          * If $_SERVER['HTTP_HOST'] is set, use that in our HELO to the SMTP
  126.          * server.  This should fix the DNS issues some people have had
  127.          */
  128.         if (sqgetGlobalVar('HTTP_HOST'$HTTP_HOSTSQ_SERVER)) // HTTP_HOST is set
  129.             // optionally trim off port number
  130.             if($p strrpos($HTTP_HOST':')) {
  131.                 $HTTP_HOST substr($HTTP_HOST0$p);
  132.             }
  133.             $helohost $HTTP_HOST;
  134.         else // For some reason, HTTP_HOST is not set - revert to old behavior
  135.             $helohost $domain;
  136.         }
  137.  
  138.         /* Lets introduce ourselves */
  139.         fputs($stream"EHLO $helohost\r\n");
  140.         // Read ehlo response
  141.         $tmp $this->parse_ehlo_response($stream);
  142.         if ($this->errorCheck($tmp,$stream)) {
  143.             // fall back to HELO if EHLO is not supported (error 5xx)
  144.             if ($this->dlv_ret_nr{0== '5'{
  145.                 fputs($stream"HELO $helohost\r\n");
  146.                 $tmp fgets($stream,1024);
  147.                 if ($this->errorCheck($tmp,$stream)) {
  148.                     return(0);
  149.                 }
  150.             else {
  151.                 return(0);
  152.             }
  153.         }
  154.  
  155.         /**
  156.          * Implementing SMTP STARTTLS (rfc2487) in php 5.1.0+
  157.          * http://www.php.net/stream-socket-enable-crypto
  158.          */
  159.         if ($use_smtp_tls === 2{
  160.             if (function_exists('stream_socket_enable_crypto')) {
  161.                 // don't try starting tls, when client thinks that it is already active
  162.                                 if ($this->tls_enabled{
  163.                     $this->dlv_msg = _("TLS session is already activated.");
  164.                     return 0;
  165.                 elseif (!array_key_exists('STARTTLS',$this->ehlo)) {
  166.                     // check for starttls in ehlo response
  167.                                         $this->dlv_msg _("SMTP STARTTLS is enabled in SquirrelMail configuration, but used SMTP server does not support it");
  168.                     return 0;
  169.                 }
  170.  
  171.                 // issue starttls command
  172.                                 fputs($stream"STARTTLS\r\n");
  173.                 // get response
  174.                                 $tmp fgets($stream,1024);
  175.                 if ($this->errorCheck($tmp,$stream)) {
  176.                     return 0;
  177.                 }
  178.  
  179.                 // start crypto on connection. suppress function errors.
  180.                                 if (@stream_socket_enable_crypto($stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
  181.                     // starttls was successful (rfc2487 5.2 Result of the STARTTLS Command)
  182.                     // get new EHLO response
  183.                                         fputs($stream"EHLO $helohost\r\n");
  184.                     // Read ehlo response
  185.                                         $tmp $this->parse_ehlo_response($stream);
  186.                     if ($this->errorCheck($tmp,$stream)) {
  187.                         // don't revert to helo here. server must support ESMTP
  188.                                                 return 0;
  189.                     }
  190.                     // set information about started tls
  191.                                         $this->tls_enabled true;
  192.                 else {
  193.                     /**
  194.                      * stream_socket_enable_crypto() call failed.
  195.                      */
  196.                     $this->dlv_msg _("Unable to start TLS.");
  197.                     return 0;
  198.                     // Bug: can't get error message. See comments in sqimap_create_stream().
  199.                                 }
  200.             else {
  201.                 // php install does not support stream_socket_enable_crypto() function
  202.                                 $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.");
  203.                 return 0;
  204.             }
  205.         }
  206.  
  207.         // FIXME: check ehlo response before using authentication
  208.                 if (( $smtp_auth_mech == 'cram-md5'or $smtp_auth_mech == 'digest-md5' )) {
  209.             // Doing some form of non-plain auth
  210.                         if ($smtp_auth_mech == 'cram-md5'{
  211.                 fputs($stream"AUTH CRAM-MD5\r\n");
  212.             elseif ($smtp_auth_mech == 'digest-md5'{
  213.                 fputs($stream"AUTH DIGEST-MD5\r\n");
  214.             }
  215.  
  216.             $tmp fgets($stream,1024);
  217.  
  218.             if ($this->errorCheck($tmp,$stream)) {
  219.                 return(0);
  220.             }
  221.  
  222.             // At this point, $tmp should hold "334 <challenge string>"
  223.                         $chall substr($tmp,4);
  224.             // Depending on mechanism, generate response string
  225.                         if ($smtp_auth_mech == 'cram-md5'{
  226.                 $response cram_md5_response($user,$pass,$chall);
  227.             elseif ($smtp_auth_mech == 'digest-md5'{
  228.                 $response digest_md5_response($user,$pass,$chall,'smtp',$host);
  229.             }
  230.             fputs($stream$response);
  231.  
  232.             // Let's see what the server had to say about that
  233.                         $tmp fgets($stream,1024);
  234.             if ($this->errorCheck($tmp,$stream)) {
  235.                 return(0);
  236.             }
  237.  
  238.             // CRAM-MD5 is done at this point.  If DIGEST-MD5, there's a bit more to go
  239.                         if ($smtp_auth_mech == 'digest-md5'{
  240.                 // $tmp contains rspauth, but I don't store that yet. (No need yet)
  241.                                 fputs($stream,"\r\n");
  242.                 $tmp fgets($stream,1024);
  243.  
  244.                 if ($this->errorCheck($tmp,$stream)) {
  245.                 return(0);
  246.                 }
  247.             }
  248.         // CRAM-MD5 and DIGEST-MD5 code ends here
  249.                 elseif ($smtp_auth_mech == 'none'{
  250.         // No auth at all, just send helo and then send the mail
  251.         // We already said hi earlier, nothing more is needed.
  252.                 elseif ($smtp_auth_mech == 'login'{
  253.             // The LOGIN method
  254.                         fputs($stream"AUTH LOGIN\r\n");
  255.             $tmp fgets($stream1024);
  256.  
  257.             if ($this->errorCheck($tmp$stream)) {
  258.                 return(0);
  259.             }
  260.             fputs($streambase64_encode ($user"\r\n");
  261.             $tmp fgets($stream1024);
  262.             if ($this->errorCheck($tmp$stream)) {
  263.                 return(0);
  264.             }
  265.  
  266.             fputs($streambase64_encode($pass"\r\n");
  267.             $tmp fgets($stream1024);
  268.             if ($this->errorCheck($tmp$stream)) {
  269.                 return(0);
  270.             }
  271.         elseif ($smtp_auth_mech == "plain"{
  272.             /* SASL Plain */
  273.             $auth base64_encode("$user\0$user\0$pass");
  274.  
  275.             $query "AUTH PLAIN\r\n";
  276.             fputs($stream$query);
  277.             $read=fgets($stream1024);
  278.  
  279.             if (substr($read,0,3== '334'// OK so far..
  280.                                 fputs($stream"$auth\r\n");
  281.                 $read fgets($stream1024);
  282.             }
  283.  
  284.             $results=explode(" ",$read,3);
  285.             $response=$results[1];
  286.             $message=$results[2];
  287.         else {
  288.             /* Right here, they've reached an unsupported auth mechanism.
  289.             This is the ugliest hack I've ever done, but it'll do till I can fix
  290.             things up better tomorrow.  So tired... */
  291.             if ($this->errorCheck("535 Unable to use this auth type",$stream)) {
  292.                 return(0);
  293.             }
  294.         }
  295.  
  296.         /* Ok, who is sending the message? */
  297.         $fromaddress ($from->mailbox && $from->host?
  298.             $from->mailbox.'@'.$from->host '';
  299.         fputs($stream'MAIL FROM:<'.$fromaddress.">\r\n");
  300.         $tmp fgets($stream1024);
  301.         if ($this->errorCheck($tmp$stream)) {
  302.             return(0);
  303.         }
  304.  
  305.         /* send who the recipients are */
  306.         for ($i 0$cnt count($to)$i $cnt$i++{
  307.             if (!$to[$i]->host$to[$i]->host $domain;
  308.             if ($to[$i]->mailbox{
  309.                 fputs($stream'RCPT TO:<'.$to[$i]->mailbox.'@'.$to[$i]->host.">\r\n");
  310.                 $tmp fgets($stream1024);
  311.                 if ($this->errorCheck($tmp$stream)) {
  312.                     return(0);
  313.                 }
  314.             }
  315.         }
  316.  
  317.         for ($i 0$cnt count($cc)$i $cnt$i++{
  318.             if (!$cc[$i]->host$cc[$i]->host $domain;
  319.             if ($cc[$i]->mailbox{
  320.                 fputs($stream'RCPT TO:<'.$cc[$i]->mailbox.'@'.$cc[$i]->host.">\r\n");
  321.                 $tmp fgets($stream1024);
  322.                 if ($this->errorCheck($tmp$stream)) {
  323.                     return(0);
  324.                 }
  325.             }
  326.         }
  327.  
  328.         for ($i 0$cnt count($bcc)$i $cnt$i++{
  329.             if (!$bcc[$i]->host$bcc[$i]->host $domain;
  330.             if ($bcc[$i]->mailbox{
  331.                 fputs($stream'RCPT TO:<'.$bcc[$i]->mailbox.'@'.$bcc[$i]->host.">\r\n");
  332.                 $tmp fgets($stream1024);
  333.                 if ($this->errorCheck($tmp$stream)) {
  334.                     return(0);
  335.                 }
  336.             }
  337.         }
  338.         /* Lets start sending the actual message */
  339.         fputs($stream"DATA\r\n");
  340.         $tmp fgets($stream1024);
  341.         if ($this->errorCheck($tmp$stream)) {
  342.                 return(0);
  343.         }
  344.         return $stream;
  345.     }
  346.  
  347.     function finalizeStream($stream{
  348.         fputs($stream"\r\n.\r\n")/* end the DATA part */
  349.         $tmp fgets($stream1024);
  350.         $this->errorCheck($tmp$stream);
  351.         if ($this->dlv_ret_nr != 250{
  352.                 return(0);
  353.         }
  354.         fputs($stream"QUIT\r\n")/* log off */
  355.         fclose($stream);
  356.         return true;
  357.     }
  358.  
  359.     /* check if an SMTP reply is an error and set an error message) */
  360.     function errorCheck($line$smtpConnection{
  361.  
  362.         $err_num substr($line03);
  363.         $this->dlv_ret_nr $err_num;
  364.         $server_msg substr($line4);
  365.  
  366.         while(substr($line04== ($err_num.'-')) {
  367.             $line fgets($smtpConnection1024);
  368.             $server_msg .= substr($line4);
  369.         }
  370.  
  371.         if ( ((int) $err_num{0}4{
  372.             return false;
  373.         }
  374.  
  375.         switch ($err_num{
  376.         case '421'$message _("Service not available, closing channel");
  377.             break;
  378.         case '432'$message _("A password transition is needed");
  379.             break;
  380.         case '450'$message _("Requested mail action not taken: mailbox unavailable");
  381.             break;
  382.         case '451'$message _("Requested action aborted: error in processing");
  383.             break;
  384.         case '452'$message _("Requested action not taken: insufficient system storage");
  385.             break;
  386.         case '454'$message _("Temporary authentication failure");
  387.             break;
  388.         case '500'$message _("Syntax error; command not recognized");
  389.             break;
  390.         case '501'$message _("Syntax error in parameters or arguments");
  391.             break;
  392.         case '502'$message _("Command not implemented");
  393.             break;
  394.         case '503'$message _("Bad sequence of commands");
  395.             break;
  396.         case '504'$message _("Command parameter not implemented");
  397.             break;
  398.         case '530'$message _("Authentication required");
  399.             break;
  400.         case '534'$message _("Authentication mechanism is too weak");
  401.             break;
  402.         case '535'$message _("Authentication failed");
  403.             break;
  404.         case '538'$message _("Encryption required for requested authentication mechanism");
  405.             break;
  406.         case '550'$message _("Requested action not taken: mailbox unavailable");
  407.             break;
  408.         case '551'$message _("User not local; please try forwarding");
  409.             break;
  410.         case '552'$message _("Requested mail action aborted: exceeding storage allocation");
  411.             break;
  412.         case '553'$message _("Requested action not taken: mailbox name not allowed");
  413.             break;
  414.         case '554'$message _("Transaction failed");
  415.             break;
  416.         default:    $message _("Unknown response");
  417.             break;
  418.         }
  419.  
  420.         $this->dlv_msg $message;
  421.         $this->dlv_server_msg $server_msg;
  422.  
  423.         return true;
  424.     }
  425.  
  426.     function authPop($pop_server=''$pop_port=''$user$pass{
  427.         if (!$pop_port{
  428.             $pop_port 110;
  429.         }
  430.         if (!$pop_server{
  431.             $pop_server 'localhost';
  432.         }
  433.         $popConnection fsockopen($pop_server$pop_port$err_no$err_str);
  434.         if (!$popConnection{
  435.             error_log("Error connecting to POP Server ($pop_server:$pop_port)"
  436.                 . " $err_no : $err_str");
  437.         else {
  438.             $tmp fgets($popConnection1024)/* banner */
  439.             if (substr($tmp03!= '+OK'{
  440.                 return(0);
  441.             }
  442.             fputs($popConnection"USER $user\r\n");
  443.             $tmp fgets($popConnection1024);
  444.             if (substr($tmp03!= '+OK'{
  445.                 return(0);
  446.             }
  447.             fputs($popConnection'PASS ' $pass "\r\n");
  448.             $tmp fgets($popConnection1024);
  449.             if (substr($tmp03!= '+OK'{
  450.                 return(0);
  451.             }
  452.             fputs($popConnection"QUIT\r\n")/* log off */
  453.             fclose($popConnection);
  454.         }
  455.     }
  456.  
  457.     /**
  458.      * Parses ESMTP EHLO response (rfc1869)
  459.      *
  460.      * Reads SMTP response to EHLO command and fills class variables
  461.      * (ehlo array and domain string). Returns last line.
  462.      * @param stream $stream smtp connection stream.
  463.      * @return string last ehlo line
  464.      * @since 1.5.1
  465.      */
  466.     function parse_ehlo_response($stream{
  467.         // don't cache ehlo information
  468.         $this->ehlo=array();
  469.         $ret '';
  470.         $firstline true;
  471.         /**
  472.          * ehlo mailclient.example.org
  473.          * 250-mail.example.org
  474.          * 250-PIPELINING
  475.          * 250-SIZE 52428800
  476.          * 250-DATAZ
  477.          * 250-STARTTLS
  478.          * 250-AUTH LOGIN PLAIN
  479.          * 250 8BITMIME
  480.          */
  481.         while ($line=fgets($stream1024)){
  482.             // match[1] = first symbol after 250
  483.             // match[2] = domain or ehlo-keyword
  484.             // match[3] = greeting or ehlo-param
  485.             // match space after keyword in ehlo-keyword CR LF
  486.             if (preg_match("/^250(-|\s)(\S*)\s+(\S.*)\r\n/",$line,$match)||
  487.                 preg_match("/^250(-|\s)(\S*)\s*\r\n/",$line,$match)) {
  488.                 if ($firstline{
  489.                     // first ehlo line (250[-\ ]domain SP greeting)
  490.                     $this->domain $match[2];
  491.                     $firstline=false;
  492.                 elseif (!isset($match[3])) {
  493.                     // simple one word extension
  494.                     $this->ehlo[strtoupper($match[2])]='';
  495.                 elseif (!preg_match("/\s/",trim($match[3]))) {
  496.                     // extension with one option
  497.                     // yes, I know about ctype extension. no, i don't want to depend on it
  498.                     $this->ehlo[strtoupper($match[2])]=trim($match[3]);
  499.                 else {
  500.                     // ehlo-param with spaces
  501.                     $this->ehlo[strtoupper($match[2])]=explode(' ',trim($match[3]));
  502.                 }
  503.                 if ($match[1]==' '{
  504.                     // stop while cycle, if we reach last 250 line
  505.                     $ret $line;
  506.                     break;
  507.                 }
  508.             else {
  509.                 // this is not 250 response
  510.                 $ret $line;
  511.                 break;
  512.             }
  513.         }
  514.         return $ret;
  515.     }
  516. }

Documentation generated on Sat, 07 Oct 2006 16:10:40 +0300 by phpDocumentor 1.3.0RC6