Source for file Deliver_SMTP.class.php
Documentation is available at Deliver_SMTP.class.php
* SMTP delivery backend for the Deliver class.
* @copyright © 1999-2006 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id: Deliver_SMTP.class.php,v 1.37 2006/07/15 12:00:44 tokul Exp $
/** This of course depends upon Deliver */
include_once(SM_PATH .
'class/deliver/Deliver.class.php');
* Deliver messages using SMTP
* Array keys are uppercased ehlo keywords
* array key values are ehlo params. If ehlo-param contains space, it is splitted into array.
* 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.
* @todo don't pass stream resource in class method arguments.
/** @var string delivery error message */
/** @var integer delivery error number from server */
/** @var string delivery error message from server */
if ($s{0} ==
'.') $s =
'.' .
$s;
function initStream($message, $domain, $length=
0, $host=
'', $port=
'', $user=
'', $pass=
'', $authpop=
false) {
$this->authPop($host, '', $user, $pass);
$rfc822_header =
$message->rfc822_header;
$from =
$rfc822_header->from[0];
$to =
$rfc822_header->to;
$cc =
$rfc822_header->cc;
$bcc =
$rfc822_header->bcc;
$content_type =
$rfc822_header->content_type;
// MAIL FROM: <from address> MUST be empty in cae of MDN (RFC2298)
if ($content_type->type0 ==
'multipart' &&
$content_type->type1 ==
'report' &&
isset
($content_type->properties['report-type']) &&
$content_type->properties['report-type']==
'disposition-notification') {
// reinitialize the from object because otherwise the from header somehow
// is affected. This $from var is used for smtp command MAIL FROM which
// is not the same as what we put in the rfc822 header.
if ($use_smtp_tls ==
1) {
$stream =
@fsockopen('tls://' .
$host, $port, $errorNumber, $errorString);
* don't connect to server when user asks for smtps and
* PHP does not support it.
$errorString =
_("Secure SMTP (TLS) is enabled in SquirrelMail configuration, but used PHP version does not support it.");
$stream =
@fsockopen($host, $port, $errorNumber, $errorString);
// reset tls state var to default value, if connection fails
$tmp =
fgets($stream, 1024);
* If $_SERVER['HTTP_HOST'] is set, use that in our HELO to the SMTP
* server. This should fix the DNS issues some people have had
// optionally trim off port number
if($p =
strrpos($HTTP_HOST, ':')) {
$HTTP_HOST =
substr($HTTP_HOST, 0, $p);
} else { // For some reason, HTTP_HOST is not set - revert to old behavior
/* Lets introduce ourselves */
fputs($stream, "EHLO $helohost\r\n");
// fall back to HELO if EHLO is not supported (error 5xx)
fputs($stream, "HELO $helohost\r\n");
$tmp =
fgets($stream,1024);
* 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
$this->dlv_msg =
_("TLS session is already activated.");
// check for starttls in ehlo response
$this->dlv_msg =
_("SMTP STARTTLS is enabled in SquirrelMail configuration, but used SMTP server does not support it");
// issue starttls command
fputs($stream, "STARTTLS\r\n");
$tmp =
fgets($stream,1024);
// 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)
fputs($stream, "EHLO $helohost\r\n");
// don't revert to helo here. server must support ESMTP
// set information about started tls
* stream_socket_enable_crypto() call failed.
$this->dlv_msg =
_("Unable to start TLS.");
// Bug: can't get error message. See comments in sqimap_create_stream().
// 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.");
// 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') {
fputs($stream, "AUTH CRAM-MD5\r\n");
} elseif ($smtp_auth_mech ==
'digest-md5') {
fputs($stream, "AUTH DIGEST-MD5\r\n");
$tmp =
fgets($stream,1024);
// At this point, $tmp should hold "334 <challenge string>"
// Depending on mechanism, generate response string
if ($smtp_auth_mech ==
'cram-md5') {
} elseif ($smtp_auth_mech ==
'digest-md5') {
fputs($stream, $response);
// Let's see what the server had to say about that
$tmp =
fgets($stream,1024);
// CRAM-MD5 is done at this point. If DIGEST-MD5, there's a bit more to go
if ($smtp_auth_mech ==
'digest-md5') {
// $tmp contains rspauth, but I don't store that yet. (No need yet)
$tmp =
fgets($stream,1024);
// CRAM-MD5 and DIGEST-MD5 code ends here
} elseif ($smtp_auth_mech ==
'none') {
// No auth at all, just send helo and then send the mail
// We already said hi earlier, nothing more is needed.
} elseif ($smtp_auth_mech ==
'login') {
fputs($stream, "AUTH LOGIN\r\n");
$tmp =
fgets($stream, 1024);
$tmp =
fgets($stream, 1024);
$tmp =
fgets($stream, 1024);
} elseif ($smtp_auth_mech ==
"plain") {
$query =
"AUTH PLAIN\r\n";
$read=
fgets($stream, 1024);
if (substr($read,0,3) ==
'334') { // OK so far..
fputs($stream, "$auth\r\n");
$read =
fgets($stream, 1024);
/* Right here, they've reached an unsupported auth mechanism.
This is the ugliest hack I've ever done, but it'll do till I can fix
things up better tomorrow. So tired... */
if ($this->errorCheck("535 Unable to use this auth type",$stream)) {
/* Ok, who is sending the message? */
$fromaddress =
($from->mailbox &&
$from->host) ?
$from->mailbox.
'@'.
$from->host :
'';
fputs($stream, 'MAIL FROM:<'.
$fromaddress.
">\r\n");
$tmp =
fgets($stream, 1024);
/* send who the recipients are */
for ($i =
0, $cnt =
count($to); $i <
$cnt; $i++
) {
if (!$to[$i]->host) $to[$i]->host =
$domain;
fputs($stream, 'RCPT TO:<'.
$to[$i]->mailbox.
'@'.
$to[$i]->host.
">\r\n");
$tmp =
fgets($stream, 1024);
for ($i =
0, $cnt =
count($cc); $i <
$cnt; $i++
) {
if (!$cc[$i]->host) $cc[$i]->host =
$domain;
fputs($stream, 'RCPT TO:<'.
$cc[$i]->mailbox.
'@'.
$cc[$i]->host.
">\r\n");
$tmp =
fgets($stream, 1024);
for ($i =
0, $cnt =
count($bcc); $i <
$cnt; $i++
) {
if (!$bcc[$i]->host) $bcc[$i]->host =
$domain;
fputs($stream, 'RCPT TO:<'.
$bcc[$i]->mailbox.
'@'.
$bcc[$i]->host.
">\r\n");
$tmp =
fgets($stream, 1024);
/* Lets start sending the actual message */
fputs($stream, "DATA\r\n");
$tmp =
fgets($stream, 1024);
fputs($stream, "\r\n.\r\n"); /* end the DATA part */
$tmp =
fgets($stream, 1024);
fputs($stream, "QUIT\r\n"); /* log off */
/* check if an SMTP reply is an error and set an error message) */
$err_num =
substr($line, 0, 3);
$server_msg =
substr($line, 4);
while(substr($line, 0, 4) ==
($err_num.
'-')) {
$line =
fgets($smtpConnection, 1024);
$server_msg .=
substr($line, 4);
if ( ((int)
$err_num{0}) <
4) {
case '421':
$message =
_("Service not available, closing channel");
case '432':
$message =
_("A password transition is needed");
case '450':
$message =
_("Requested mail action not taken: mailbox unavailable");
case '451':
$message =
_("Requested action aborted: error in processing");
case '452':
$message =
_("Requested action not taken: insufficient system storage");
case '454':
$message =
_("Temporary authentication failure");
case '500':
$message =
_("Syntax error; command not recognized");
case '501':
$message =
_("Syntax error in parameters or arguments");
case '502':
$message =
_("Command not implemented");
case '503':
$message =
_("Bad sequence of commands");
case '504':
$message =
_("Command parameter not implemented");
case '530':
$message =
_("Authentication required");
case '534':
$message =
_("Authentication mechanism is too weak");
case '535':
$message =
_("Authentication failed");
case '538':
$message =
_("Encryption required for requested authentication mechanism");
case '550':
$message =
_("Requested action not taken: mailbox unavailable");
case '551':
$message =
_("User not local; please try forwarding");
case '552':
$message =
_("Requested mail action aborted: exceeding storage allocation");
case '553':
$message =
_("Requested action not taken: mailbox name not allowed");
case '554':
$message =
_("Transaction failed");
default:
$message =
_("Unknown response");
function authPop($pop_server=
'', $pop_port=
'', $user, $pass) {
$pop_server =
'localhost';
$popConnection =
fsockopen($pop_server, $pop_port, $err_no, $err_str);
error_log("Error connecting to POP Server ($pop_server:$pop_port)"
.
" $err_no : $err_str");
$tmp =
fgets($popConnection, 1024); /* banner */
if (substr($tmp, 0, 3) !=
'+OK') {
fputs($popConnection, "USER $user\r\n");
$tmp =
fgets($popConnection, 1024);
if (substr($tmp, 0, 3) !=
'+OK') {
fputs($popConnection, 'PASS ' .
$pass .
"\r\n");
$tmp =
fgets($popConnection, 1024);
if (substr($tmp, 0, 3) !=
'+OK') {
fputs($popConnection, "QUIT\r\n"); /* log off */
* 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
// don't cache ehlo information
* ehlo mailclient.example.org
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)) {
// first ehlo line (250[-\ ]domain SP greeting)
} elseif (!isset
($match[3])) {
// simple one word extension
// extension with one option
// yes, I know about ctype extension. no, i don't want to depend on it
// ehlo-param with spaces
// stop while cycle, if we reach last 250 line
// this is not 250 response
Documentation generated on Sat, 07 Oct 2006 16:10:40 +0300 by phpDocumentor 1.3.0RC6