185 protected function edebug($str, $level = 0)
187 if ($level > $this->do_debug) {
190 if (is_callable($this->Debugoutput)) {
191 call_user_func($this->Debugoutput, $str, $this->do_debug);
194 switch ($this->Debugoutput) {
202 preg_replace(
'/[\r\n]+/',
'', $str),
211 $str = preg_replace(
'/(\r\n|\r|\n)/ms',
"\n", $str);
212 echo gmdate(
'Y-m-d H:i:s') .
"\t" . str_replace(
229 public function connect($host, $port = null, $timeout = 30, $options = array())
234 if (is_null($streamok)) {
235 $streamok = function_exists(
'stream_socket_client');
238 $this->error = array();
242 $this->error = array(
'error' =>
'Already connected to a server');
246 $port = self::DEFAULT_SMTP_PORT;
250 "Connection: opening to $host:$port, t=$timeout, opt=".var_export($options,
true),
251 self::DEBUG_CONNECTION
256 $socket_context = stream_context_create($options);
258 $this->smtp_conn = @stream_socket_client(
263 STREAM_CLIENT_CONNECT,
269 "Connection: stream_socket_client not available, falling back to fsockopen",
270 self::DEBUG_CONNECTION
272 $this->smtp_conn = fsockopen(
281 if (!is_resource($this->smtp_conn)) {
282 $this->error = array(
283 'error' =>
'Failed to connect to server',
288 'SMTP ERROR: ' . $this->error[
'error']
289 .
": $errstr ($errno)",
294 $this->
edebug(
'Connection: opened', self::DEBUG_CONNECTION);
297 if (substr(PHP_OS, 0, 3) !=
'WIN') {
298 $max = ini_get(
'max_execution_time');
299 if ($max != 0 && $timeout > $max) {
300 @set_time_limit($timeout);
302 stream_set_timeout($this->smtp_conn, $timeout, 0);
306 $this->
edebug(
'SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
317 if (!$this->
sendCommand(
'STARTTLS',
'STARTTLS', 220)) {
321 if (!stream_socket_enable_crypto(
324 STREAM_CRYPTO_METHOD_TLS_CLIENT
350 if (empty($authtype)) {
356 if (!$this->
sendCommand(
'AUTH',
'AUTH PLAIN', 334)) {
362 base64_encode(
"\0" . $username .
"\0" . $password),
371 if (!$this->
sendCommand(
'AUTH',
'AUTH LOGIN', 334)) {
374 if (!$this->
sendCommand(
"Username", base64_encode($username), 334)) {
377 if (!$this->
sendCommand(
"Password", base64_encode($password), 235)) {
390 require_once
'extras/ntlm_sasl_client.php';
391 $temp =
new stdClass();
392 $ntlm_client =
new ntlm_sasl_client_class;
394 if (!$ntlm_client->Initialize($temp)) {
395 $this->error = array(
'error' => $temp->error);
397 'You need to enable some modules in your php.ini file: '
398 . $this->error[
'error'],
404 $msg1 = $ntlm_client->TypeMsg1($realm, $workstation);
408 'AUTH NTLM ' . base64_encode($msg1),
416 $challenge = substr($this->last_reply, 3);
417 $challenge = base64_decode($challenge);
418 $ntlm_res = $ntlm_client->NTLMResponse(
419 substr($challenge, 24, 8),
423 $msg3 = $ntlm_client->TypeMsg3(
430 return $this->
sendCommand(
'Username', base64_encode($msg3), 235);
433 if (!$this->
sendCommand(
'AUTH CRAM-MD5',
'AUTH CRAM-MD5', 334)) {
437 $challenge = base64_decode(substr($this->last_reply, 4));
440 $response = $username .
' ' . $this->
hmac($challenge, $password);
443 return $this->
sendCommand(
'Username', base64_encode($response), 235);
457 protected function hmac($data, $key)
459 if (function_exists(
'hash_hmac')) {
460 return hash_hmac(
'md5', $data, $key);
472 if (strlen($key) > $bytelen) {
473 $key = pack(
'H*', md5($key));
475 $key = str_pad($key, $bytelen, chr(0x00));
476 $ipad = str_pad(
'', $bytelen, chr(0x36));
477 $opad = str_pad(
'', $bytelen, chr(0x5c));
478 $k_ipad = $key ^ $ipad;
479 $k_opad = $key ^ $opad;
481 return md5($k_opad . pack(
'H*', md5($k_ipad . $data)));
491 if (is_resource($this->smtp_conn)) {
492 $sock_status = stream_get_meta_data($this->smtp_conn);
493 if ($sock_status[
'eof']) {
496 'SMTP NOTICE: EOF caught while checking if connected',
516 $this->error = array();
517 $this->helo_rply = null;
518 if (is_resource($this->smtp_conn)) {
520 fclose($this->smtp_conn);
521 $this->smtp_conn = null;
522 $this->
edebug(
'Connection: closed', self::DEBUG_CONNECTION);
538 public function data($msg_data)
552 $lines = explode(
"\n", str_replace(array(
"\r\n",
"\r"),
"\n", $msg_data));
559 $field = substr($lines[0], 0, strpos($lines[0],
':'));
561 if (!empty($field) && strpos($field,
' ') ===
false) {
565 foreach ($lines as $line) {
566 $lines_out = array();
567 if ($in_headers and $line ==
'') {
572 while (isset($line[self::MAX_LINE_LENGTH])) {
575 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH),
' ');
578 $pos = self::MAX_LINE_LENGTH - 1;
579 $lines_out[] = substr($line, 0, $pos);
580 $line = substr($line, $pos);
583 $lines_out[] = substr($line, 0, $pos);
585 $line = substr($line, $pos + 1);
591 $line =
"\t" . $line;
594 $lines_out[] = $line;
597 foreach ($lines_out as $line_out) {
599 if (!empty($line_out) and $line_out[0] ==
'.') {
600 $line_out =
'.' . $line_out;
637 $noerror = $this->
sendCommand($hello, $hello .
' ' . $host, 250);
655 $useVerp = ($this->do_verp ?
' XVERP' :
'');
658 'MAIL FROM:<' . $from .
'>' . $useVerp,
671 public function quit($close_on_error =
true)
673 $noerror = $this->
sendCommand(
'QUIT',
'QUIT', 221);
675 if ($noerror or $close_on_error) {
695 'RCPT TO:<' . $toaddr .
'>',
723 $this->error = array(
724 'error' =>
"Called $command without being connected"
731 $code = substr($this->last_reply, 0, 3);
733 $this->
edebug(
'SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
735 if (!in_array($code, (array)$expect)) {
736 $this->error = array(
737 'error' =>
"$command command failed",
738 'smtp_code' => $code,
739 'detail' => substr($this->last_reply, 4)
742 'SMTP ERROR: ' . $this->error[
'error'] .
': ' . $this->last_reply,
748 $this->error = array();
767 return $this->
sendCommand(
'SAML',
"SAML FROM:$from", 250);
778 return $this->
sendCommand(
'VRFY',
"VRFY $name", array(250, 251));
803 $this->error = array(
804 'error' =>
'The SMTP TURN command is not implemented'
806 $this->
edebug(
'SMTP NOTICE: ' . $this->error[
'error'], self::DEBUG_CLIENT);
818 $this->
edebug(
"CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
819 return fwrite($this->smtp_conn, $data);
854 if (!is_resource($this->smtp_conn)) {
859 stream_set_timeout($this->smtp_conn, $this->Timeout);
860 if ($this->Timelimit > 0) {
863 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
864 $str = @fgets($this->smtp_conn, 515);
865 $this->
edebug(
"SMTP -> get_lines(): \$data was \"$data\"", self::DEBUG_LOWLEVEL);
866 $this->
edebug(
"SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
868 $this->
edebug(
"SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
870 if ((isset($str[3]) and $str[3] ==
' ')) {
874 $info = stream_get_meta_data($this->smtp_conn);
875 if ($info[
'timed_out']) {
877 'SMTP -> get_lines(): timed-out (' . $this->Timeout .
' sec)',
883 if ($endtime and time() > $endtime) {
885 'SMTP -> get_lines(): timelimit reached ('.
886 $this->Timelimit .
' sec)',
901 $this->do_verp = $enabled;
919 $this->Debugoutput = $method;
937 $this->do_debug = $level;
955 $this->Timeout = $timeout;