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');
274 $port = self::DEFAULT_SMTP_PORT;
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)) {
460 if (!$this->
sendCommand(
"Password", base64_encode($password), 235)) {
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));
536 $response = $username .
' ' . $this->
hmac($challenge, $password);
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);
911 "$command command failed",
917 'SMTP ERROR: ' . $this->error[
'error'] .
': ' . $this->last_reply,
942 return $this->
sendCommand(
'SAML',
"SAML FROM:$from", 250);
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)) {
connect($host, $port=null, $timeout=30, $options=array())
Connect to an SMTP server.
const DEBUG_SERVER
Debug level to show client -> server and server -> client messages.
noop()
Send an SMTP NOOP command.
getTimeout()
Get SMTP timeout.
hmac($data, $key)
Calculate an MD5 HMAC hash.
getError()
Get the latest error.
sendCommand($command, $commandstring, $expect)
Send a command to an SMTP server and check its return code.
getDebugLevel()
Get debug output level.
getLastReply()
Get the last reply from the server.
const DEBUG_CLIENT
Debug level to show client -> server messages.
errorHandler($errno, $errmsg, $errfile='', $errline=0)
Reports an error number and string.
hello($host='')
Send an SMTP HELO or EHLO command.
const DEBUG_LOWLEVEL
Debug level to show all messages.
setDebugLevel($level=0)
Set debug output level.
$smtp_transaction_id_patterns
getServerExt($name)
A multipurpose method The method works in three ways, dependent on argument value and current state...
setVerp($enabled=false)
Enable or disable VERP address generation.
sendHello($hello, $host)
Send an SMTP HELO or EHLO command.
close()
Close the socket and clean up the state of the class.
catch(Exception $e) $message
data($msg_data)
Send an SMTP DATA command.
setTimeout($timeout=0)
Set SMTP timeout.
connected()
Check connection state.
sendAndMail($from)
Send an SMTP SAML command.
getVerp()
Get VERP address generation mode.
authenticate( $username, $password, $authtype=null, $realm='', $workstation='', $OAuth=null)
Perform SMTP authentication.
startTLS()
Initiate a TLS (encrypted) session.
getServerExtList()
Get SMTP extensions available on the server public.
setDebugOutput($method='echo')
Set debug output method.
getDebugOutput()
Get debug output method.
recipient($address)
Send an SMTP RCPT command.
const DEBUG_OFF
Debug level for no output.
client_send($data)
Send raw data to the server.
Create styles array
The data for the language used.
reset()
Send an SMTP RSET command.
parseHelloFields($type)
Parse a reply to HELO/EHLO command to discover server extensions.
mail($from)
Send an SMTP MAIL command.
edebug($str, $level=0)
Output debugging info via a user-selected method.
const DEBUG_CONNECTION
Debug level to show connection status, client -> server and server -> client messages.
get_lines()
Read the SMTP server's response.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
getLastTransactionID()
Will return the ID of the last smtp transaction based on a list of patterns provided in SMTP::$smtp_t...
quit($close_on_error=true)
Send an SMTP QUIT command.
turn()
Send an SMTP TURN command.
if(!isset($_REQUEST['ReturnTo'])) if(!isset($_REQUEST['AuthId'])) $options
verify($name)
Send an SMTP VRFY command.
setError($message, $detail='', $smtp_code='', $smtp_code_ex='')
Set error messages and codes.