159        'exim' => 
'/[0-9]{3} OK id=(.*)/',
 
  160        'sendmail' => 
'/[0-9]{3} 2.0.0 (.*) Message/',
 
  161        'postfix' => 
'/[0-9]{3} 2.0.0 Ok: queued as (.*)/' 
  213    protected function edebug($str, $level = 0)
 
  215        if ($level > $this->do_debug) {
 
  219        if (!in_array($this->Debugoutput, array(
'error_log', 
'html', 
'echo')) and is_callable($this->Debugoutput)) {
 
  220            call_user_func($this->Debugoutput, $str, $level);
 
  223        switch ($this->Debugoutput) {
 
  230                echo gmdate(
'Y-m-d H:i:s') . 
' ' . htmlentities(
 
  231                    preg_replace(
'/[\r\n]+/', 
'', $str),
 
  239                $str = preg_replace(
'/(\r\n|\r|\n)/ms', 
"\n", $str);
 
  240                echo gmdate(
'Y-m-d H:i:s') . 
"\t" . str_replace(
 
  262        if (is_null($streamok)) {
 
  263            $streamok = function_exists(
'stream_socket_client');
 
  270            $this->
setError(
'Already connected to a server');
 
  278            "Connection: opening to $host:$port, timeout=$timeout, options=" .
 
  280            self::DEBUG_CONNECTION
 
  285            $socket_context = stream_context_create(
$options);
 
  286            set_error_handler(array($this, 
'errorHandler'));
 
  287            $this->smtp_conn = stream_socket_client(
 
  292                STREAM_CLIENT_CONNECT,
 
  295            restore_error_handler();
 
  299                "Connection: stream_socket_client not available, falling back to fsockopen",
 
  300                self::DEBUG_CONNECTION
 
  302            set_error_handler(array($this, 
'errorHandler'));
 
  303            $this->smtp_conn = fsockopen(
 
  310            restore_error_handler();
 
  313        if (!is_resource($this->smtp_conn)) {
 
  315                'Failed to connect to server',
 
  320                'SMTP ERROR: ' . $this->
error[
'error']
 
  321                . 
": $errstr ($errno)",
 
  326        $this->
edebug(
'Connection: opened', self::DEBUG_CONNECTION);
 
  329        if (substr(PHP_OS, 0, 3) != 
'WIN') {
 
  330            $max = ini_get(
'max_execution_time');
 
  332            if ($max != 0 && $timeout > $max) {
 
  333                @set_time_limit($timeout);
 
  335            stream_set_timeout($this->smtp_conn, $timeout, 0);
 
  339        $this->
edebug(
'SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
 
  350        if (!$this->
sendCommand(
'STARTTLS', 
'STARTTLS', 220)) {
 
  355        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
 
  359        if (
defined(
'STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
 
  360            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
 
  361            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
 
  365        set_error_handler(array($this, 
'errorHandler'));
 
  366        $crypto_ok = stream_socket_enable_crypto(
 
  371        restore_error_handler();
 
  395        if (!$this->server_caps) {
 
  396            $this->
setError(
'Authentication is not allowed before HELO/EHLO');
 
  400        if (array_key_exists(
'EHLO', $this->server_caps)) {
 
  402            if (!array_key_exists(
'AUTH', $this->server_caps)) {
 
  403                $this->
setError(
'Authentication is not allowed at this stage');
 
  409            self::edebug(
'Auth method requested: ' . ($authtype ? $authtype : 
'UNKNOWN'), self::DEBUG_LOWLEVEL);
 
  411                'Auth methods available on the server: ' . implode(
',', $this->server_caps[
'AUTH']),
 
  415            if (empty($authtype)) {
 
  416                foreach (array(
'CRAM-MD5', 
'LOGIN', 
'PLAIN', 
'NTLM', 
'XOAUTH2') as $method) {
 
  417                    if (in_array($method, $this->server_caps[
'AUTH'])) {
 
  422                if (empty($authtype)) {
 
  423                    $this->
setError(
'No supported authentication methods found');
 
  426                self::edebug(
'Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
 
  429            if (!in_array($authtype, $this->server_caps[
'AUTH'])) {
 
  430                $this->
setError(
"The requested authentication method \"$authtype\" is not supported by the server");
 
  433        } elseif (empty($authtype)) {
 
  439                if (!$this->
sendCommand(
'AUTH', 
'AUTH PLAIN', 334)) {
 
  445                    base64_encode(
"\0" . $username . 
"\0" . 
$password),
 
  454                if (!$this->
sendCommand(
'AUTH', 
'AUTH LOGIN', 334)) {
 
  457                if (!$this->
sendCommand(
"Username", base64_encode($username), 334)) {
 
  467                if (is_null($OAuth)) {
 
  470                $oauth = $OAuth->getOauth64();
 
  473                if (!$this->
sendCommand(
'AUTH', 
'AUTH XOAUTH2 ' . $oauth, 235)) {
 
  486                require_once 
'extras/ntlm_sasl_client.php';
 
  487                $temp = 
new stdClass;
 
  490                if (!$ntlm_client->initialize($temp)) {
 
  493                        'You need to enable some modules in your php.ini file: ' 
  494                        . $this->
error[
'error'],
 
  500                $msg1 = $ntlm_client->typeMsg1($realm, $workstation); 
 
  504                    'AUTH NTLM ' . base64_encode($msg1),
 
  512                $challenge = substr($this->last_reply, 3);
 
  513                $challenge = base64_decode($challenge);
 
  514                $ntlm_res = $ntlm_client->NTLMResponse(
 
  515                    substr($challenge, 24, 8),
 
  519                $msg3 = $ntlm_client->typeMsg3(
 
  526                return $this->
sendCommand(
'Username', base64_encode($msg3), 235);
 
  529                if (!$this->
sendCommand(
'AUTH CRAM-MD5', 
'AUTH CRAM-MD5', 334)) {
 
  533                $challenge = base64_decode(substr($this->last_reply, 4));
 
  541                $this->
setError(
"Authentication method \"$authtype\" is not supported");
 
  558        if (function_exists(
'hash_hmac')) {
 
  571        if (strlen(
$key) > $bytelen) {
 
  574        $key = str_pad(
$key, $bytelen, chr(0x00));
 
  575        $ipad = str_pad(
'', $bytelen, chr(0x36));
 
  576        $opad = str_pad(
'', $bytelen, chr(0x5c));
 
  577        $k_ipad = 
$key ^ $ipad;
 
  578        $k_opad = 
$key ^ $opad;
 
  580        return md5($k_opad . pack(
'H*', md5($k_ipad . 
$data)));
 
  590        if (is_resource($this->smtp_conn)) {
 
  591            $sock_status = stream_get_meta_data($this->smtp_conn);
 
  592            if ($sock_status[
'eof']) {
 
  595                    'SMTP NOTICE: EOF caught while checking if connected',
 
  616        $this->server_caps = 
null;
 
  617        $this->helo_rply = 
null;
 
  618        if (is_resource($this->smtp_conn)) {
 
  620            fclose($this->smtp_conn);
 
  621            $this->smtp_conn = 
null; 
 
  622            $this->
edebug(
'Connection: closed', self::DEBUG_CONNECTION);
 
  638    public function data($msg_data)
 
  654        $lines = explode(
"\n", str_replace(array(
"\r\n", 
"\r"), 
"\n", $msg_data));
 
  661        $field = substr($lines[0], 0, strpos($lines[0], 
':'));
 
  663        if (!empty($field) && strpos($field, 
' ') === 
false) {
 
  667        foreach ($lines as $line) {
 
  668            $lines_out = array();
 
  669            if ($in_headers and $line == 
'') {
 
  674            while (isset($line[self::MAX_LINE_LENGTH])) {
 
  677                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), 
' ');
 
  681                    $pos = self::MAX_LINE_LENGTH - 1;
 
  682                    $lines_out[] = substr($line, 0, $pos);
 
  683                    $line = substr($line, $pos);
 
  686                    $lines_out[] = substr($line, 0, $pos);
 
  688                    $line = substr($line, $pos + 1);
 
  692                    $line = 
"\t" . $line;
 
  695            $lines_out[] = $line;
 
  698            foreach ($lines_out as $line_out) {
 
  700                if (!empty($line_out) and $line_out[0] == 
'.') {
 
  701                    $line_out = 
'.' . $line_out;
 
  710        $this->Timelimit = $this->Timelimit * 2;
 
  713        $this->Timelimit = $savetimelimit;
 
  744        $noerror = $this->
sendCommand($hello, $hello . 
' ' . $host, 250);
 
  749            $this->server_caps = 
null;
 
  762        $this->server_caps = array();
 
  763        $lines = explode(
"\n", $this->helo_rply);
 
  765        foreach ($lines as 
$n => 
$s) {
 
  767            $s = trim(substr(
$s, 4));
 
  771            $fields = explode(
' ', 
$s);
 
  772            if (!empty($fields)) {
 
  775                    $fields = $fields[0];
 
  777                    $name = array_shift($fields);
 
  780                            $fields = ($fields ? $fields[0] : 0);
 
  783                            if (!is_array($fields)) {
 
  791                $this->server_caps[
$name] = $fields;
 
  809        $useVerp = ($this->do_verp ? 
' XVERP' : 
'');
 
  812            'MAIL FROM:<' . 
$from . 
'>' . $useVerp,
 
  825    public function quit($close_on_error = 
true)
 
  827        $noerror = $this->
sendCommand(
'QUIT', 
'QUIT', 221);
 
  829        if ($noerror or $close_on_error) {
 
  849            'RCPT TO:<' . $address . 
'>',
 
  877            $this->
setError(
"Called $command without being connected");
 
  881        if (strpos($commandstring, 
"\n") !== 
false or strpos($commandstring, 
"\r") !== 
false) {
 
  882            $this->
setError(
"Command '$command' contained line breaks");
 
  890        if (preg_match(
"/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
 
  892            $code_ex = (count($matches) > 2 ? $matches[2] : 
null);
 
  894            $detail = preg_replace(
 
  896                ($code_ex ? str_replace(
'.', 
'\\.', $code_ex) . 
' ' : 
'') . 
"/m",
 
  902            $code = substr($this->last_reply, 0, 3);
 
  904            $detail = substr($this->last_reply, 4);
 
  907        $this->
edebug(
'SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
 
  909        if (!in_array(
$code, (array)$expect)) {
 
  911                "$command command failed",
 
  917                'SMTP ERROR: ' . $this->
error[
'error'] . 
': ' . $this->last_reply,
 
  942        return $this->
sendCommand(
'SAML', 
"SAML FROM:$from", 250);
 
  953        return $this->
sendCommand(
'VRFY', 
"VRFY $name", array(250, 251));
 
  978        $this->
setError(
'The SMTP TURN command is not implemented');
 
  979        $this->
edebug(
'SMTP NOTICE: ' . $this->
error[
'error'], self::DEBUG_CLIENT);
 
  991        $this->
edebug(
"CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
 
  992        set_error_handler(array($this, 
'errorHandler'));
 
  994        restore_error_handler();
 
 1039        if (!$this->server_caps) {
 
 1040            $this->
setError(
'No HELO/EHLO was sent');
 
 1045        if (!array_key_exists(
$name, $this->server_caps)) {
 
 1046            if (
$name == 
'HELO') {
 
 1047                return $this->server_caps[
'EHLO'];
 
 1049            if (
$name == 
'EHLO' || array_key_exists(
'EHLO', $this->server_caps)) {
 
 1052            $this->
setError(
'HELO handshake was used. Client knows nothing about server extensions');
 
 1056        return $this->server_caps[
$name];
 
 1081        if (!is_resource($this->smtp_conn)) {
 
 1086        stream_set_timeout($this->smtp_conn, $this->Timeout);
 
 1087        if ($this->Timelimit > 0) {
 
 1090        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
 
 1091            $str = @fgets($this->smtp_conn, 515);
 
 1092            $this->
edebug(
"SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
 
 1093            $this->
edebug(
"SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
 
 1098            if (!isset($str[3]) or (isset($str[3]) and $str[3] == 
' ')) {
 
 1102            $info = stream_get_meta_data($this->smtp_conn);
 
 1103            if (
$info[
'timed_out']) {
 
 1105                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . 
' sec)',
 
 1106                    self::DEBUG_LOWLEVEL
 
 1111            if ($endtime and time() > $endtime) {
 
 1113                    'SMTP -> get_lines(): timelimit reached (' .
 
 1114                    $this->Timelimit . 
' sec)',
 
 1115                    self::DEBUG_LOWLEVEL
 
 1129        $this->do_verp = $enabled;
 
 1150        $this->
error = array(
 
 1152            'detail' => $detail,
 
 1153            'smtp_code' => $smtp_code,
 
 1154            'smtp_code_ex' => $smtp_code_ex
 
 1164        $this->Debugoutput = $method;
 
 1182        $this->do_debug = $level;
 
 1200        $this->Timeout = $timeout;
 
 1219    protected function errorHandler($errno, $errmsg, $errfile = 
'', $errline = 0)
 
 1221        $notice = 
'Connection failed.';
 
 1228            $notice . 
' Error #' . $errno . 
': ' . $errmsg . 
" [$errfile line $errline]",
 
 1229            self::DEBUG_CONNECTION
 
 1244        if (empty($reply)) {
 
 1248        foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
 
 1249            if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
 
if(!isset( $_REQUEST[ 'ReturnTo'])) if(!isset($_REQUEST['AuthId'])) $options
An exception for terminatinating execution or to throw for unit testing.
setDebugOutput($method='echo')
Set debug output method.
getLastReply()
Get the last reply from the server.
mail($from)
Send an SMTP MAIL command.
setError($message, $detail='', $smtp_code='', $smtp_code_ex='')
Set error messages and codes.
turn()
Send an SMTP TURN command.
getServerExtList()
Get SMTP extensions available on the server @access public.
close()
Close the socket and clean up the state of the class.
connected()
Check connection state.
getVerp()
Get VERP address generation mode.
quit($close_on_error=true)
Send an SMTP QUIT command.
sendAndMail($from)
Send an SMTP SAML command.
const DEBUG_CONNECTION
Debug level to show connection status, client -> server and server -> client messages.
hmac($data, $key)
Calculate an MD5 HMAC hash.
getDebugLevel()
Get debug output level.
client_send($data)
Send raw data to the server.
sendCommand($command, $commandstring, $expect)
Send a command to an SMTP server and check its return code.
setVerp($enabled=false)
Enable or disable VERP address generation.
noop()
Send an SMTP NOOP command.
reset()
Send an SMTP RSET command.
const DEBUG_LOWLEVEL
Debug level to show all messages.
const DEBUG_SERVER
Debug level to show client -> server and server -> client messages.
edebug($str, $level=0)
Output debugging info via a user-selected method.
connect($host, $port=null, $timeout=30, $options=array())
Connect to an SMTP server.
setTimeout($timeout=0)
Set SMTP timeout.
get_lines()
Read the SMTP server's response.
getDebugOutput()
Get debug output method.
parseHelloFields($type)
Parse a reply to HELO/EHLO command to discover server extensions.
verify($name)
Send an SMTP VRFY command.
setDebugLevel($level=0)
Set debug output level.
getServerExt($name)
A multipurpose method The method works in three ways, dependent on argument value and current state.
errorHandler($errno, $errmsg, $errfile='', $errline=0)
Reports an error number and string.
$smtp_transaction_id_patterns
const DEBUG_OFF
Debug level for no output.
authenticate( $username, $password, $authtype=null, $realm='', $workstation='', $OAuth=null)
Perform SMTP authentication.
const DEBUG_CLIENT
Debug level to show client -> server messages.
getTimeout()
Get SMTP timeout.
data($msg_data)
Send an SMTP DATA command.
getError()
Get the latest error.
recipient($address)
Send an SMTP RCPT command.
startTLS()
Initiate a TLS (encrypted) session.
sendHello($hello, $host)
Send an SMTP HELO or EHLO command.
hello($host='')
Send an SMTP HELO or EHLO command.
getLastTransactionID()
Will return the ID of the last smtp transaction based on a list of patterns provided in SMTP::$smtp_t...
error($a_errmsg)
set error message @access public
catch(Exception $e) $message
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'