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) {
231 preg_replace(
'/[\r\n]+/',
'', $str),
240 $str = preg_replace(
'/(\r\n|\r|\n)/ms',
"\n", $str);
241 echo gmdate(
'Y-m-d H:i:s') .
"\t" . str_replace(
263 if (is_null($streamok)) {
264 $streamok = function_exists(
'stream_socket_client');
271 $this->
setError(
'Already connected to a server');
275 $port = self::DEFAULT_SMTP_PORT;
279 "Connection: opening to $host:$port, timeout=$timeout, options=".var_export(
$options,
true),
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 if (!stream_socket_enable_crypto(
395 if (!$this->server_caps) {
396 $this->
setError(
'Authentication is not allowed before HELO/EHLO');
400 if (array_key_exists(
'EHLO', $this->server_caps)) {
403 if (!array_key_exists(
'AUTH', $this->server_caps)) {
404 $this->
setError(
'Authentication is not allowed at this stage');
410 self::edebug(
'Auth method requested: ' . ($authtype ? $authtype :
'UNKNOWN'), self::DEBUG_LOWLEVEL);
412 'Auth methods available on the server: ' . implode(
',', $this->server_caps[
'AUTH']),
416 if (empty($authtype)) {
417 foreach (
array(
'CRAM-MD5',
'LOGIN',
'PLAIN',
'NTLM',
'XOAUTH2') as $method) {
418 if (in_array($method, $this->server_caps[
'AUTH'])) {
423 if (empty($authtype)) {
424 $this->
setError(
'No supported authentication methods found');
427 self::edebug(
'Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
430 if (!in_array($authtype, $this->server_caps[
'AUTH'])) {
431 $this->
setError(
"The requested authentication method \"$authtype\" is not supported by the server");
434 } elseif (empty($authtype)) {
440 if (!$this->
sendCommand(
'AUTH',
'AUTH PLAIN', 334)) {
446 base64_encode(
"\0" . $username .
"\0" . $password),
455 if (!$this->
sendCommand(
'AUTH',
'AUTH LOGIN', 334)) {
458 if (!$this->
sendCommand(
"Username", base64_encode($username), 334)) {
461 if (!$this->
sendCommand(
"Password", base64_encode($password), 235)) {
468 if (is_null($OAuth)) {
471 $oauth = $OAuth->getOauth64();
474 if (!$this->
sendCommand(
'AUTH',
'AUTH XOAUTH2 ' . $oauth, 235)) {
487 require_once
'extras/ntlm_sasl_client.php';
488 $temp =
new stdClass;
491 if (!$ntlm_client->initialize($temp)) {
494 'You need to enable some modules in your php.ini file: ' 495 . $this->error[
'error'],
501 $msg1 = $ntlm_client->typeMsg1($realm, $workstation);
505 'AUTH NTLM ' . base64_encode($msg1),
513 $challenge = substr($this->last_reply, 3);
514 $challenge = base64_decode($challenge);
515 $ntlm_res = $ntlm_client->NTLMResponse(
516 substr($challenge, 24, 8),
520 $msg3 = $ntlm_client->typeMsg3(
527 return $this->
sendCommand(
'Username', base64_encode($msg3), 235);
530 if (!$this->
sendCommand(
'AUTH CRAM-MD5',
'AUTH CRAM-MD5', 334)) {
534 $challenge = base64_decode(substr($this->last_reply, 4));
537 $response = $username .
' ' . $this->
hmac($challenge, $password);
540 return $this->
sendCommand(
'Username', base64_encode($response), 235);
542 $this->
setError(
"Authentication method \"$authtype\" is not supported");
559 if (function_exists(
'hash_hmac')) {
560 return hash_hmac(
'md5',
$data, $key);
572 if (strlen($key) > $bytelen) {
573 $key = pack(
'H*', md5($key));
575 $key = str_pad($key, $bytelen, chr(0x00));
576 $ipad = str_pad(
'', $bytelen, chr(0x36));
577 $opad = str_pad(
'', $bytelen, chr(0x5c));
578 $k_ipad = $key ^ $ipad;
579 $k_opad = $key ^ $opad;
581 return md5($k_opad . pack(
'H*', md5($k_ipad .
$data)));
591 if (is_resource($this->smtp_conn)) {
592 $sock_status = stream_get_meta_data($this->smtp_conn);
593 if ($sock_status[
'eof']) {
596 'SMTP NOTICE: EOF caught while checking if connected',
617 $this->server_caps = null;
618 $this->helo_rply = null;
619 if (is_resource($this->smtp_conn)) {
621 fclose($this->smtp_conn);
622 $this->smtp_conn = null;
623 $this->
edebug(
'Connection: closed', self::DEBUG_CONNECTION);
639 public function data($msg_data)
655 $lines = explode(
"\n", str_replace(
array(
"\r\n",
"\r"),
"\n", $msg_data));
662 $field = substr($lines[0], 0, strpos($lines[0],
':'));
664 if (!empty($field) && strpos($field,
' ') ===
false) {
668 foreach ($lines as $line) {
669 $lines_out =
array();
670 if ($in_headers
and $line ==
'') {
675 while (isset($line[self::MAX_LINE_LENGTH])) {
678 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH),
' ');
682 $pos = self::MAX_LINE_LENGTH - 1;
683 $lines_out[] = substr($line, 0, $pos);
684 $line = substr($line, $pos);
687 $lines_out[] = substr($line, 0, $pos);
689 $line = substr($line, $pos + 1);
693 $line =
"\t" . $line;
696 $lines_out[] = $line;
699 foreach ($lines_out as $line_out) {
701 if (!empty($line_out)
and $line_out[0] ==
'.') {
702 $line_out =
'.' . $line_out;
711 $this->Timelimit = $this->Timelimit * 2;
714 $this->Timelimit = $savetimelimit;
745 $noerror = $this->
sendCommand($hello, $hello .
' ' . $host, 250);
750 $this->server_caps = null;
763 $this->server_caps =
array();
764 $lines = explode(
"\n", $this->helo_rply);
766 foreach ($lines as
$n => $s) {
768 $s = trim(substr($s, 4));
772 $fields = explode(
' ', $s);
773 if (!empty($fields)) {
776 $fields = $fields[0];
778 $name = array_shift($fields);
781 $fields = ($fields ? $fields[0] : 0);
784 if (!is_array($fields)) {
792 $this->server_caps[$name] = $fields;
810 $useVerp = ($this->do_verp ?
' XVERP' :
'');
813 'MAIL FROM:<' . $from .
'>' . $useVerp,
826 public function quit($close_on_error =
true)
828 $noerror = $this->
sendCommand(
'QUIT',
'QUIT', 221);
830 if ($noerror
or $close_on_error) {
850 'RCPT TO:<' . $address .
'>',
878 $this->
setError(
"Called $command without being connected");
882 if (strpos($commandstring,
"\n") !==
false or strpos($commandstring,
"\r") !==
false) {
883 $this->
setError(
"Command '$command' contained line breaks");
891 if (preg_match(
"/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
893 $code_ex = (count($matches) > 2 ? $matches[2] : null);
895 $detail = preg_replace(
896 "/{$code}[ -]".($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 return fwrite($this->smtp_conn,
$data);
1036 if (!$this->server_caps) {
1037 $this->
setError(
'No HELO/EHLO was sent');
1042 if (!array_key_exists($name, $this->server_caps)) {
1043 if ($name ==
'HELO') {
1044 return $this->server_caps[
'EHLO'];
1046 if ($name ==
'EHLO' || array_key_exists(
'EHLO', $this->server_caps)) {
1049 $this->
setError(
'HELO handshake was used. Client knows nothing about server extensions');
1053 return $this->server_caps[$name];
1078 if (!is_resource($this->smtp_conn)) {
1083 stream_set_timeout($this->smtp_conn, $this->Timeout);
1084 if ($this->Timelimit > 0) {
1087 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1088 $str = @fgets($this->smtp_conn, 515);
1089 $this->
edebug(
"SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
1090 $this->
edebug(
"SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
1093 if ((isset($str[3])
and $str[3] ==
' ')) {
1097 $info = stream_get_meta_data($this->smtp_conn);
1098 if (
$info[
'timed_out']) {
1100 'SMTP -> get_lines(): timed-out (' . $this->Timeout .
' sec)',
1101 self::DEBUG_LOWLEVEL
1106 if ($endtime
and time() > $endtime) {
1108 'SMTP -> get_lines(): timelimit reached ('.
1109 $this->Timelimit .
' sec)',
1110 self::DEBUG_LOWLEVEL
1124 $this->do_verp = $enabled;
1143 protected function setError($message, $detail =
'', $smtp_code =
'', $smtp_code_ex =
'')
1145 $this->error =
array(
1146 'error' => $message,
1147 'detail' => $detail,
1148 'smtp_code' => $smtp_code,
1149 'smtp_code_ex' => $smtp_code_ex
1159 $this->Debugoutput = $method;
1177 $this->do_debug = $level;
1195 $this->Timeout = $timeout;
1214 $notice =
'Connection: Failed to connect to server.';
1221 $notice .
' Error number ' . $errno .
'. "Error notice: ' . $errmsg,
1222 self::DEBUG_CONNECTION
1237 if (empty($reply)) {
1241 foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1242 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.
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.
errorHandler($errno, $errmsg)
Reports an error number and string.
data($msg_data)
Send an SMTP DATA command.
setTimeout($timeout=0)
Set SMTP timeout.
if(!is_array($argv)) $options
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.
verify($name)
Send an SMTP VRFY command.
setError($message, $detail='', $smtp_code='', $smtp_code_ex='')
Set error messages and codes.