202 protected function edebug($str, $level = 0)
204 if ($level > $this->do_debug) {
208 if (!in_array($this->Debugoutput, array(
'error_log',
'html',
'echo')) and is_callable($this->Debugoutput)) {
209 call_user_func($this->Debugoutput, $str, $this->do_debug);
212 switch ($this->Debugoutput) {
220 preg_replace(
'/[\r\n]+/',
'', $str),
229 $str = preg_replace(
'/(\r\n|\r|\n)/ms',
"\n", $str);
230 echo gmdate(
'Y-m-d H:i:s') .
"\t" . str_replace(
247 public function connect($host, $port = null, $timeout = 30, $options = array())
252 if (is_null($streamok)) {
253 $streamok = function_exists(
'stream_socket_client');
260 $this->
setError(
'Already connected to a server');
264 $port = self::DEFAULT_SMTP_PORT;
268 "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options,
true),
269 self::DEBUG_CONNECTION
274 $socket_context = stream_context_create($options);
276 $this->smtp_conn = @stream_socket_client(
281 STREAM_CLIENT_CONNECT,
287 "Connection: stream_socket_client not available, falling back to fsockopen",
288 self::DEBUG_CONNECTION
290 $this->smtp_conn = fsockopen(
299 if (!is_resource($this->smtp_conn)) {
301 'Failed to connect to server',
306 'SMTP ERROR: ' . $this->error[
'error']
307 .
": $errstr ($errno)",
312 $this->
edebug(
'Connection: opened', self::DEBUG_CONNECTION);
315 if (substr(PHP_OS, 0, 3) !=
'WIN') {
316 $max = ini_get(
'max_execution_time');
318 if ($max != 0 && $timeout > $max) {
319 @set_time_limit($timeout);
321 stream_set_timeout($this->smtp_conn, $timeout, 0);
325 $this->
edebug(
'SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
336 if (!$this->
sendCommand(
'STARTTLS',
'STARTTLS', 220)) {
340 if (!stream_socket_enable_crypto(
343 STREAM_CRYPTO_METHOD_TLS_CLIENT
370 if (!$this->server_caps) {
371 $this->
setError(
'Authentication is not allowed before HELO/EHLO');
375 if (array_key_exists(
'EHLO', $this->server_caps)) {
378 if (!array_key_exists(
'AUTH', $this->server_caps)) {
379 $this->
setError(
'Authentication is not allowed at this stage');
385 self::edebug(
'Auth method requested: ' . ($authtype ? $authtype :
'UNKNOWN'), self::DEBUG_LOWLEVEL);
387 'Auth methods available on the server: ' . implode(
',', $this->server_caps[
'AUTH']),
391 if (empty($authtype)) {
392 foreach (array(
'LOGIN',
'CRAM-MD5',
'NTLM',
'PLAIN',
'XOAUTH2') as
$method) {
393 if (in_array($method, $this->server_caps[
'AUTH'])) {
398 if (empty($authtype)) {
399 $this->
setError(
'No supported authentication methods found');
402 self::edebug(
'Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
405 if (!in_array($authtype, $this->server_caps[
'AUTH'])) {
406 $this->
setError(
"The requested authentication method \"$authtype\" is not supported by the server");
409 } elseif (empty($authtype)) {
415 if (!$this->
sendCommand(
'AUTH',
'AUTH PLAIN', 334)) {
421 base64_encode(
"\0" . $username .
"\0" . $password),
430 if (!$this->
sendCommand(
'AUTH',
'AUTH LOGIN', 334)) {
433 if (!$this->
sendCommand(
"Username", base64_encode($username), 334)) {
436 if (!$this->
sendCommand(
"Password", base64_encode($password), 235)) {
443 if (is_null($OAuth)) {
446 $oauth = $OAuth->getOauth64();
449 if (!$this->
sendCommand(
'AUTH',
'AUTH XOAUTH2 ' . $oauth, 235)) {
462 require_once
'extras/ntlm_sasl_client.php';
463 $temp =
new stdClass;
464 $ntlm_client =
new ntlm_sasl_client_class;
466 if (!$ntlm_client->Initialize($temp)) {
469 'You need to enable some modules in your php.ini file: '
470 . $this->error[
'error'],
476 $msg1 = $ntlm_client->TypeMsg1($realm, $workstation);
480 'AUTH NTLM ' . base64_encode($msg1),
488 $challenge = substr($this->last_reply, 3);
489 $challenge = base64_decode($challenge);
490 $ntlm_res = $ntlm_client->NTLMResponse(
491 substr($challenge, 24, 8),
495 $msg3 = $ntlm_client->TypeMsg3(
502 return $this->
sendCommand(
'Username', base64_encode($msg3), 235);
505 if (!$this->
sendCommand(
'AUTH CRAM-MD5',
'AUTH CRAM-MD5', 334)) {
509 $challenge = base64_decode(substr($this->last_reply, 4));
512 $response = $username .
' ' . $this->
hmac($challenge, $password);
515 return $this->
sendCommand(
'Username', base64_encode($response), 235);
517 $this->
setError(
"Authentication method \"$authtype\" is not supported");
534 if (function_exists(
'hash_hmac')) {
535 return hash_hmac(
'md5',
$data, $key);
547 if (strlen($key) > $bytelen) {
548 $key = pack(
'H*', md5($key));
550 $key = str_pad($key, $bytelen, chr(0x00));
551 $ipad = str_pad(
'', $bytelen, chr(0x36));
552 $opad = str_pad(
'', $bytelen, chr(0x5c));
553 $k_ipad = $key ^ $ipad;
554 $k_opad = $key ^ $opad;
556 return md5($k_opad . pack(
'H*', md5($k_ipad .
$data)));
566 if (is_resource($this->smtp_conn)) {
567 $sock_status = stream_get_meta_data($this->smtp_conn);
568 if ($sock_status[
'eof']) {
571 'SMTP NOTICE: EOF caught while checking if connected',
592 $this->server_caps = null;
593 $this->helo_rply = null;
594 if (is_resource($this->smtp_conn)) {
596 fclose($this->smtp_conn);
597 $this->smtp_conn = null;
598 $this->
edebug(
'Connection: closed', self::DEBUG_CONNECTION);
614 public function data($msg_data)
630 $lines = explode(
"\n", str_replace(array(
"\r\n",
"\r"),
"\n", $msg_data));
637 $field = substr($lines[0], 0, strpos($lines[0],
':'));
639 if (!empty($field) && strpos($field,
' ') ===
false) {
643 foreach ($lines as $line) {
644 $lines_out = array();
645 if ($in_headers and $line ==
'') {
650 while (isset($line[self::MAX_LINE_LENGTH])) {
653 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH),
' ');
657 $pos = self::MAX_LINE_LENGTH - 1;
658 $lines_out[] = substr($line, 0, $pos);
659 $line = substr($line, $pos);
662 $lines_out[] = substr($line, 0, $pos);
664 $line = substr($line, $pos + 1);
668 $line =
"\t" . $line;
671 $lines_out[] = $line;
674 foreach ($lines_out as $line_out) {
676 if (!empty($line_out) and $line_out[0] ==
'.') {
677 $line_out =
'.' . $line_out;
686 $this->Timelimit = $this->Timelimit * 2;
687 $result = $this->
sendCommand(
'DATA END',
'.', 250);
689 $this->Timelimit = $savetimelimit;
720 $noerror = $this->
sendCommand($hello, $hello .
' ' . $host, 250);
725 $this->server_caps = null;
738 $this->server_caps = array();
739 $lines = explode(
"\n", $this->last_reply);
741 foreach ($lines as $n =>
$s) {
743 $s = trim(substr(
$s, 4));
747 $fields = explode(
' ',
$s);
748 if (!empty($fields)) {
751 $fields = $fields[0];
753 $name = array_shift($fields);
756 $fields = ($fields ? $fields[0] : 0);
759 if (!is_array($fields)) {
767 $this->server_caps[$name] = $fields;
785 $useVerp = ($this->do_verp ?
' XVERP' :
'');
788 'MAIL FROM:<' .
$from .
'>' . $useVerp,
801 public function quit($close_on_error =
true)
803 $noerror = $this->
sendCommand(
'QUIT',
'QUIT', 221);
805 if ($noerror or $close_on_error) {
825 'RCPT TO:<' . $address .
'>',
853 $this->
setError(
"Called $command without being connected");
857 if (strpos($commandstring,
"\n") !==
false or strpos($commandstring,
"\r") !==
false) {
858 $this->
setError(
"Command '$command' contained line breaks");
866 if (preg_match(
"/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
868 $code_ex = (count($matches) > 2 ? $matches[2] : null);
870 $detail = preg_replace(
871 "/{$code}[ -]".($code_ex ? str_replace(
'.',
'\\.', $code_ex).
' ' :
'').
"/m",
877 $code = substr($this->last_reply, 0, 3);
879 $detail = substr($this->last_reply, 4);
882 $this->
edebug(
'SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
884 if (!in_array($code, (array)$expect)) {
886 "$command command failed",
892 'SMTP ERROR: ' . $this->error[
'error'] .
': ' . $this->last_reply,
917 return $this->
sendCommand(
'SAML',
"SAML FROM:$from", 250);
928 return $this->
sendCommand(
'VRFY',
"VRFY $name", array(250, 251));
953 $this->
setError(
'The SMTP TURN command is not implemented');
954 $this->
edebug(
'SMTP NOTICE: ' . $this->error[
'error'], self::DEBUG_CLIENT);
966 $this->
edebug(
"CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
967 return fwrite($this->smtp_conn,
$data);
1011 if (!$this->server_caps) {
1012 $this->
setError(
'No HELO/EHLO was sent');
1017 if (!array_key_exists($name, $this->server_caps)) {
1018 if ($name ==
'HELO') {
1019 return $this->server_caps[
'EHLO'];
1021 if ($name ==
'EHLO' || array_key_exists(
'EHLO', $this->server_caps)) {
1024 $this->
setError(
'HELO handshake was used. Client knows nothing about server extensions');
1028 return $this->server_caps[$name];
1053 if (!is_resource($this->smtp_conn)) {
1058 stream_set_timeout($this->smtp_conn, $this->Timeout);
1059 if ($this->Timelimit > 0) {
1062 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1063 $str = @fgets($this->smtp_conn, 515);
1064 $this->
edebug(
"SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
1065 $this->
edebug(
"SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
1068 if ((isset($str[3]) and $str[3] ==
' ')) {
1072 $info = stream_get_meta_data($this->smtp_conn);
1073 if (
$info[
'timed_out']) {
1075 'SMTP -> get_lines(): timed-out (' . $this->Timeout .
' sec)',
1076 self::DEBUG_LOWLEVEL
1081 if ($endtime and time() > $endtime) {
1083 'SMTP -> get_lines(): timelimit reached ('.
1084 $this->Timelimit .
' sec)',
1085 self::DEBUG_LOWLEVEL
1099 $this->do_verp = $enabled;
1120 $this->error = array(
1122 'detail' => $detail,
1123 'smtp_code' => $smtp_code,
1124 'smtp_code_ex' => $smtp_code_ex
1152 $this->do_debug = $level;
1170 $this->Timeout = $timeout;
connect($host, $port=null, $timeout=30, $options=array())
setDebugOutput($method= 'echo')
authenticate($username, $password, $authtype=null, $realm= '', $workstation= '', $OAuth=null)
sendCommand($command, $commandstring, $expect)
if($file) if($subject==""&&$file) if($subject=="") $err
quit($close_on_error=true)
setError($message, $detail= '', $smtp_code= '', $smtp_code_ex= '')