class.smtp.php Quellcode

class.smtp.php
gehe zur Dokumentation dieser Datei
1 <?php
27 class SMTP
28 {
33  const VERSION = '5.2.9';
34 
39  const CRLF = "\r\n";
40 
45  const DEFAULT_SMTP_PORT = 25;
46 
51  const MAX_LINE_LENGTH = 998;
52 
56  const DEBUG_OFF = 0;
57 
61  const DEBUG_CLIENT = 1;
62 
66  const DEBUG_SERVER = 2;
67 
71  const DEBUG_CONNECTION = 3;
72 
76  const DEBUG_LOWLEVEL = 4;
77 
84  public $Version = '5.2.9';
85 
92  public $SMTP_PORT = 25;
93 
100  public $CRLF = "\r\n";
101 
112  public $do_debug = self::DEBUG_OFF;
113 
127  public $Debugoutput = 'echo';
128 
135  public $do_verp = false;
136 
144  public $Timeout = 300;
145 
150  public $Timelimit = 30;
151 
156  protected $smtp_conn;
157 
162  protected $error = array();
163 
169  protected $helo_rply = null;
170 
175  protected $last_reply = '';
176 
185  protected function edebug($str, $level = 0)
186  {
187  if ($level > $this->do_debug) {
188  return;
189  }
190  if (is_callable($this->Debugoutput)) {
191  call_user_func($this->Debugoutput, $str, $this->do_debug);
192  return;
193  }
194  switch ($this->Debugoutput) {
195  case 'error_log':
196  //Don't output, just log
197  error_log($str);
198  break;
199  case 'html':
200  //Cleans up output a bit for a better looking, HTML-safe output
201  echo htmlentities(
202  preg_replace('/[\r\n]+/', '', $str),
203  ENT_QUOTES,
204  'UTF-8'
205  )
206  . "<br>\n";
207  break;
208  case 'echo':
209  default:
210  //Normalize line breaks
211  $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
212  echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
213  "\n",
214  "\n \t ",
215  trim($str)
216  )."\n";
217  }
218  }
219 
229  public function connect($host, $port = null, $timeout = 30, $options = array())
230  {
231  static $streamok;
232  //This is enabled by default since 5.0.0 but some providers disable it
233  //Check this once and cache the result
234  if (is_null($streamok)) {
235  $streamok = function_exists('stream_socket_client');
236  }
237  // Clear errors to avoid confusion
238  $this->error = array();
239  // Make sure we are __not__ connected
240  if ($this->connected()) {
241  // Already connected, generate error
242  $this->error = array('error' => 'Already connected to a server');
243  return false;
244  }
245  if (empty($port)) {
246  $port = self::DEFAULT_SMTP_PORT;
247  }
248  // Connect to the SMTP server
249  $this->edebug(
250  "Connection: opening to $host:$port, t=$timeout, opt=".var_export($options, true),
251  self::DEBUG_CONNECTION
252  );
253  $errno = 0;
254  $errstr = '';
255  if ($streamok) {
256  $socket_context = stream_context_create($options);
257  //Suppress errors; connection failures are handled at a higher level
258  $this->smtp_conn = @stream_socket_client(
259  $host . ":" . $port,
260  $errno,
261  $errstr,
262  $timeout,
263  STREAM_CLIENT_CONNECT,
264  $socket_context
265  );
266  } else {
267  //Fall back to fsockopen which should work in more places, but is missing some features
268  $this->edebug(
269  "Connection: stream_socket_client not available, falling back to fsockopen",
270  self::DEBUG_CONNECTION
271  );
272  $this->smtp_conn = fsockopen(
273  $host,
274  $port,
275  $errno,
276  $errstr,
277  $timeout
278  );
279  }
280  // Verify we connected properly
281  if (!is_resource($this->smtp_conn)) {
282  $this->error = array(
283  'error' => 'Failed to connect to server',
284  'errno' => $errno,
285  'errstr' => $errstr
286  );
287  $this->edebug(
288  'SMTP ERROR: ' . $this->error['error']
289  . ": $errstr ($errno)",
290  self::DEBUG_CLIENT
291  );
292  return false;
293  }
294  $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
295  // SMTP server can take longer to respond, give longer timeout for first read
296  // Windows does not have support for this timeout function
297  if (substr(PHP_OS, 0, 3) != 'WIN') {
298  $max = ini_get('max_execution_time');
299  if ($max != 0 && $timeout > $max) { // Don't bother if unlimited
300  @set_time_limit($timeout);
301  }
302  stream_set_timeout($this->smtp_conn, $timeout, 0);
303  }
304  // Get any announcement
305  $announce = $this->get_lines();
306  $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
307  return true;
308  }
309 
315  public function startTLS()
316  {
317  if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
318  return false;
319  }
320  // Begin encrypted connection
321  if (!stream_socket_enable_crypto(
322  $this->smtp_conn,
323  true,
324  STREAM_CRYPTO_METHOD_TLS_CLIENT
325  )) {
326  return false;
327  }
328  return true;
329  }
330 
343  public function authenticate(
344  $username,
345  $password,
346  $authtype = 'LOGIN',
347  $realm = '',
348  $workstation = ''
349  ) {
350  if (empty($authtype)) {
351  $authtype = 'LOGIN';
352  }
353  switch ($authtype) {
354  case 'PLAIN':
355  // Start authentication
356  if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
357  return false;
358  }
359  // Send encoded username and password
360  if (!$this->sendCommand(
361  'User & Password',
362  base64_encode("\0" . $username . "\0" . $password),
363  235
364  )
365  ) {
366  return false;
367  }
368  break;
369  case 'LOGIN':
370  // Start authentication
371  if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
372  return false;
373  }
374  if (!$this->sendCommand("Username", base64_encode($username), 334)) {
375  return false;
376  }
377  if (!$this->sendCommand("Password", base64_encode($password), 235)) {
378  return false;
379  }
380  break;
381  case 'NTLM':
382  /*
383  * ntlm_sasl_client.php
384  * Bundled with Permission
385  *
386  * How to telnet in windows:
387  * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
388  * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
389  */
390  require_once 'extras/ntlm_sasl_client.php';
391  $temp = new stdClass();
392  $ntlm_client = new ntlm_sasl_client_class;
393  //Check that functions are available
394  if (!$ntlm_client->Initialize($temp)) {
395  $this->error = array('error' => $temp->error);
396  $this->edebug(
397  'You need to enable some modules in your php.ini file: '
398  . $this->error['error'],
399  self::DEBUG_CLIENT
400  );
401  return false;
402  }
403  //msg1
404  $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1
405 
406  if (!$this->sendCommand(
407  'AUTH NTLM',
408  'AUTH NTLM ' . base64_encode($msg1),
409  334
410  )
411  ) {
412  return false;
413  }
414  //Though 0 based, there is a white space after the 3 digit number
415  //msg2
416  $challenge = substr($this->last_reply, 3);
417  $challenge = base64_decode($challenge);
418  $ntlm_res = $ntlm_client->NTLMResponse(
419  substr($challenge, 24, 8),
420  $password
421  );
422  //msg3
423  $msg3 = $ntlm_client->TypeMsg3(
424  $ntlm_res,
425  $username,
426  $realm,
427  $workstation
428  );
429  // send encoded username
430  return $this->sendCommand('Username', base64_encode($msg3), 235);
431  case 'CRAM-MD5':
432  // Start authentication
433  if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
434  return false;
435  }
436  // Get the challenge
437  $challenge = base64_decode(substr($this->last_reply, 4));
438 
439  // Build the response
440  $response = $username . ' ' . $this->hmac($challenge, $password);
441 
442  // send encoded credentials
443  return $this->sendCommand('Username', base64_encode($response), 235);
444  }
445  return true;
446  }
447 
457  protected function hmac($data, $key)
458  {
459  if (function_exists('hash_hmac')) {
460  return hash_hmac('md5', $data, $key);
461  }
462 
463  // The following borrowed from
464  // http://php.net/manual/en/function.mhash.php#27225
465 
466  // RFC 2104 HMAC implementation for php.
467  // Creates an md5 HMAC.
468  // Eliminates the need to install mhash to compute a HMAC
469  // by Lance Rushing
470 
471  $bytelen = 64; // byte length for md5
472  if (strlen($key) > $bytelen) {
473  $key = pack('H*', md5($key));
474  }
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;
480 
481  return md5($k_opad . pack('H*', md5($k_ipad . $data)));
482  }
483 
489  public function connected()
490  {
491  if (is_resource($this->smtp_conn)) {
492  $sock_status = stream_get_meta_data($this->smtp_conn);
493  if ($sock_status['eof']) {
494  // The socket is valid but we are not connected
495  $this->edebug(
496  'SMTP NOTICE: EOF caught while checking if connected',
497  self::DEBUG_CLIENT
498  );
499  $this->close();
500  return false;
501  }
502  return true; // everything looks good
503  }
504  return false;
505  }
506 
514  public function close()
515  {
516  $this->error = array();
517  $this->helo_rply = null;
518  if (is_resource($this->smtp_conn)) {
519  // close the connection and cleanup
520  fclose($this->smtp_conn);
521  $this->smtp_conn = null; //Makes for cleaner serialization
522  $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
523  }
524  }
525 
538  public function data($msg_data)
539  {
540  if (!$this->sendCommand('DATA', 'DATA', 354)) {
541  return false;
542  }
543  /* The server is ready to accept data!
544  * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
545  * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
546  * smaller lines to fit within the limit.
547  * We will also look for lines that start with a '.' and prepend an additional '.'.
548  * NOTE: this does not count towards line-length limit.
549  */
550 
551  // Normalize line breaks before exploding
552  $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
553 
554  /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
555  * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
556  * process all lines before a blank line as headers.
557  */
558 
559  $field = substr($lines[0], 0, strpos($lines[0], ':'));
560  $in_headers = false;
561  if (!empty($field) && strpos($field, ' ') === false) {
562  $in_headers = true;
563  }
564 
565  foreach ($lines as $line) {
566  $lines_out = array();
567  if ($in_headers and $line == '') {
568  $in_headers = false;
569  }
570  // ok we need to break this line up into several smaller lines
571  //This is a small micro-optimisation: isset($str[$len]) is equivalent to (strlen($str) > $len)
572  while (isset($line[self::MAX_LINE_LENGTH])) {
573  //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
574  //so as to avoid breaking in the middle of a word
575  $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
576  if (!$pos) { //Deliberately matches both false and 0
577  //No nice break found, add a hard break
578  $pos = self::MAX_LINE_LENGTH - 1;
579  $lines_out[] = substr($line, 0, $pos);
580  $line = substr($line, $pos);
581  } else {
582  //Break at the found point
583  $lines_out[] = substr($line, 0, $pos);
584  //Move along by the amount we dealt with
585  $line = substr($line, $pos + 1);
586  }
587  /* If processing headers add a LWSP-char to the front of new line
588  * RFC822 section 3.1.1
589  */
590  if ($in_headers) {
591  $line = "\t" . $line;
592  }
593  }
594  $lines_out[] = $line;
595 
596  // Send the lines to the server
597  foreach ($lines_out as $line_out) {
598  //RFC2821 section 4.5.2
599  if (!empty($line_out) and $line_out[0] == '.') {
600  $line_out = '.' . $line_out;
601  }
602  $this->client_send($line_out . self::CRLF);
603  }
604  }
605 
606  // Message data has been sent, complete the command
607  return $this->sendCommand('DATA END', '.', 250);
608  }
609 
620  public function hello($host = '')
621  {
622  // Try extended hello first (RFC 2821)
623  return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
624  }
625 
635  protected function sendHello($hello, $host)
636  {
637  $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
638  $this->helo_rply = $this->last_reply;
639  return $noerror;
640  }
641 
653  public function mail($from)
654  {
655  $useVerp = ($this->do_verp ? ' XVERP' : '');
656  return $this->sendCommand(
657  'MAIL FROM',
658  'MAIL FROM:<' . $from . '>' . $useVerp,
659  250
660  );
661  }
662 
671  public function quit($close_on_error = true)
672  {
673  $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
674  $err = $this->error; //Save any error
675  if ($noerror or $close_on_error) {
676  $this->close();
677  $this->error = $err; //Restore any error from the quit command
678  }
679  return $noerror;
680  }
681 
691  public function recipient($toaddr)
692  {
693  return $this->sendCommand(
694  'RCPT TO',
695  'RCPT TO:<' . $toaddr . '>',
696  array(250, 251)
697  );
698  }
699 
707  public function reset()
708  {
709  return $this->sendCommand('RSET', 'RSET', 250);
710  }
711 
720  protected function sendCommand($command, $commandstring, $expect)
721  {
722  if (!$this->connected()) {
723  $this->error = array(
724  'error' => "Called $command without being connected"
725  );
726  return false;
727  }
728  $this->client_send($commandstring . self::CRLF);
729 
730  $this->last_reply = $this->get_lines();
731  $code = substr($this->last_reply, 0, 3);
732 
733  $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
734 
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)
740  );
741  $this->edebug(
742  'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
743  self::DEBUG_CLIENT
744  );
745  return false;
746  }
747 
748  $this->error = array();
749  return true;
750  }
751 
765  public function sendAndMail($from)
766  {
767  return $this->sendCommand('SAML', "SAML FROM:$from", 250);
768  }
769 
776  public function verify($name)
777  {
778  return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
779  }
780 
787  public function noop()
788  {
789  return $this->sendCommand('NOOP', 'NOOP', 250);
790  }
791 
801  public function turn()
802  {
803  $this->error = array(
804  'error' => 'The SMTP TURN command is not implemented'
805  );
806  $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
807  return false;
808  }
809 
816  public function client_send($data)
817  {
818  $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
819  return fwrite($this->smtp_conn, $data);
820  }
821 
827  public function getError()
828  {
829  return $this->error;
830  }
831 
837  public function getLastReply()
838  {
839  return $this->last_reply;
840  }
841 
851  protected function get_lines()
852  {
853  // If the connection is bad, give up straight away
854  if (!is_resource($this->smtp_conn)) {
855  return '';
856  }
857  $data = '';
858  $endtime = 0;
859  stream_set_timeout($this->smtp_conn, $this->Timeout);
860  if ($this->Timelimit > 0) {
861  $endtime = time() + $this->Timelimit;
862  }
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);
867  $data .= $str;
868  $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
869  // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
870  if ((isset($str[3]) and $str[3] == ' ')) {
871  break;
872  }
873  // Timed-out? Log and break
874  $info = stream_get_meta_data($this->smtp_conn);
875  if ($info['timed_out']) {
876  $this->edebug(
877  'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
878  self::DEBUG_LOWLEVEL
879  );
880  break;
881  }
882  // Now check if reads took too long
883  if ($endtime and time() > $endtime) {
884  $this->edebug(
885  'SMTP -> get_lines(): timelimit reached ('.
886  $this->Timelimit . ' sec)',
887  self::DEBUG_LOWLEVEL
888  );
889  break;
890  }
891  }
892  return $data;
893  }
894 
899  public function setVerp($enabled = false)
900  {
901  $this->do_verp = $enabled;
902  }
903 
908  public function getVerp()
909  {
910  return $this->do_verp;
911  }
912 
917  public function setDebugOutput($method = 'echo')
918  {
919  $this->Debugoutput = $method;
920  }
921 
926  public function getDebugOutput()
927  {
928  return $this->Debugoutput;
929  }
930 
935  public function setDebugLevel($level = 0)
936  {
937  $this->do_debug = $level;
938  }
939 
944  public function getDebugLevel()
945  {
946  return $this->do_debug;
947  }
948 
953  public function setTimeout($timeout = 0)
954  {
955  $this->Timeout = $timeout;
956  }
957 
962  public function getTimeout()
963  {
964  return $this->Timeout;
965  }
966 }




Korrekturen, Hinweise und Ergänzungen

Bitte scheuen Sie sich nicht und melden Sie, was auf dieser Seite sachlich falsch oder irreführend ist, was ergänzt werden sollte, was fehlt usw. Dazu bitte oben aus dem Menü Seite den Eintrag Support Forum wählen. Es ist eine kostenlose Anmeldung erforderlich, um Anmerkungen zu posten. Unpassende Postings, Spam usw. werden kommentarlos entfernt.