Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
smtp.php
Aller à la documentation de ce fichier.
1 <?php
2 /*~ class.smtp.php
3 .---------------------------------------------------------------------------.
4 | Software: PHPMailer - PHP email class |
5 | Version: 5.2.6 |
6 | Site: https://github.com/PHPMailer/PHPMailer/ |
7 | ------------------------------------------------------------------------- |
8 | Admins: Marcus Bointon |
9 | Admins: Jim Jagielski |
10 | Authors: Andy Prevost (codeworxtech) codeworxtech@users.sourceforge.net |
11 | : Marcus Bointon (coolbru) phpmailer@synchromedia.co.uk |
12 | : Jim Jagielski (jimjag) jimjag@gmail.com |
13 | Founder: Brent R. Matzelle (original founder) |
14 | Copyright (c) 2010-2012, Jim Jagielski. All Rights Reserved. |
15 | Copyright (c) 2004-2009, Andy Prevost. All Rights Reserved. |
16 | Copyright (c) 2001-2003, Brent R. Matzelle |
17 | ------------------------------------------------------------------------- |
18 | License: Distributed under the Lesser General Public License (LGPL) |
19 | http://www.gnu.org/copyleft/lesser.html |
20 | This program is distributed in the hope that it will be useful - WITHOUT |
21 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
22 | FITNESS FOR A PARTICULAR PURPOSE. |
23 '---------------------------------------------------------------------------'
24 */
25 
26 /**
27  * PHPMailer - PHP SMTP email transport class
28  * NOTE: Designed for use with PHP version 5 and up
29  * @package PHPMailer
30  * @author Andy Prevost
31  * @author Marcus Bointon
32  * @copyright 2004 - 2008 Andy Prevost
33  * @author Jim Jagielski
34  * @copyright 2010 - 2012 Jim Jagielski
35  * @license http://www.gnu.org/copyleft/lesser.html Distributed under the Lesser General Public License (LGPL)
36  */
37 
38 /**
39  * PHP RFC821 SMTP client
40  *
41  * Implements all the RFC 821 SMTP commands except TURN which will always return a not implemented error.
42  * SMTP also provides some utility methods for sending mail to an SMTP server.
43  * @author Chris Ryan
44  * @package PHPMailer
45  */
46 
47 class SMTP {
48  /**
49  * SMTP server port
50  * @var int
51  */
52  public $SMTP_PORT = 25;
53 
54  /**
55  * SMTP reply line ending (don't change)
56  * @var string
57  */
58  public $CRLF = "\r\n";
59 
60  /**
61  * Debug output level; 0 for no output
62  * @var int
63  */
64  public $do_debug = 0;
65 
66  /**
67  * Sets the function/method to use for debugging output.
68  * Right now we only honor 'echo', 'html' or 'error_log'
69  * @var string
70  */
71  public $Debugoutput = 'echo';
72 
73  /**
74  * Sets VERP use on/off (default is off)
75  * @var bool
76  */
77  public $do_verp = false;
78 
79  /**
80  * Sets the SMTP timeout value for reads, in seconds
81  * @var int
82  */
83  public $Timeout = 15;
84 
85  /**
86  * Sets the SMTP timelimit value for reads, in seconds
87  * @var int
88  */
89  public $Timelimit = 30;
90 
91  /**
92  * Sets the SMTP PHPMailer Version number
93  * @var string
94  */
95  public $Version = '5.2.6';
96 
97  /////////////////////////////////////////////////
98  // PROPERTIES, PRIVATE AND PROTECTED
99  /////////////////////////////////////////////////
100 
101  /**
102  * @var resource The socket to the server
103  */
104  protected $smtp_conn;
105  /**
106  * @var string Error message, if any, for the last call
107  */
108  protected $error;
109  /**
110  * @var string The reply the server sent to us for HELO
111  */
112  protected $helo_rply;
113 
114  /**
115  * Outputs debugging info via user-defined method
116  * @param string $str
117  */
118  protected function edebug($str) {
119  switch ($this->Debugoutput) {
120  case 'error_log':
121  error_log($str);
122  break;
123  case 'html':
124  //Cleans up output a bit for a better looking display that's HTML-safe
125  echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8')."<br>\n";
126  break;
127  case 'echo':
128  default:
129  //Just echoes exactly what was received
130  echo $str;
131  }
132  }
133 
134  /**
135  * Initialize the class so that the data is in a known state.
136  * @access public
137  * @return SMTP
138  */
139  public function __construct() {
140  $this->smtp_conn = 0;
141  $this->error = null;
142  $this->helo_rply = null;
143 
144  $this->do_debug = 0;
145  }
146 
147  /////////////////////////////////////////////////
148  // CONNECTION FUNCTIONS
149  /////////////////////////////////////////////////
150 
151  /**
152  * Connect to an SMTP server
153  *
154  * SMTP CODE SUCCESS: 220
155  * SMTP CODE FAILURE: 421
156  * @access public
157  * @param string $host SMTP server IP or host name
158  * @param int $port The port number to connect to, or use the default port if not specified
159  * @param int $timeout How long to wait for the connection to open
160  * @param array $options An array of options compatible with stream_context_create()
161  * @return bool
162  */
163  public function Connect($host, $port = 0, $timeout = 30, $options = array()) {
164  // Clear errors to avoid confusion
165  $this->error = null;
166 
167  // Make sure we are __not__ connected
168  if($this->connected()) {
169  // Already connected, generate error
170  $this->error = array('error' => 'Already connected to a server');
171  return false;
172  }
173 
174  if(empty($port)) {
175  $port = $this->SMTP_PORT;
176  }
177 
178  // Connect to the SMTP server
179  $errno = 0;
180  $errstr = '';
181  $socket_context = stream_context_create($options);
182  //Need to suppress errors here as connection failures can be handled at a higher level
183  $this->smtp_conn = @stream_socket_client($host.":".$port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $socket_context);
184 
185  // Verify we connected properly
186  if(empty($this->smtp_conn)) {
187  $this->error = array('error' => 'Failed to connect to server',
188  'errno' => $errno,
189  'errstr' => $errstr);
190  if($this->do_debug >= 1) {
191  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ": $errstr ($errno)");
192  }
193  return false;
194  }
195 
196  // SMTP server can take longer to respond, give longer timeout for first read
197  // Windows does not have support for this timeout function
198  if(substr(PHP_OS, 0, 3) != 'WIN') {
199  $max = ini_get('max_execution_time');
200  if ($max != 0 && $timeout > $max) { // Don't bother if unlimited
201  @set_time_limit($timeout);
202  }
203  stream_set_timeout($this->smtp_conn, $timeout, 0);
204  }
205 
206  // get any announcement
207  $announce = $this->get_lines();
208 
209  if($this->do_debug >= 2) {
210  $this->edebug('SMTP -> FROM SERVER:' . $announce);
211  }
212 
213  return true;
214  }
215 
216  /**
217  * Initiate a TLS communication with the server.
218  *
219  * SMTP CODE 220 Ready to start TLS
220  * SMTP CODE 501 Syntax error (no parameters allowed)
221  * SMTP CODE 454 TLS not available due to temporary reason
222  * @access public
223  * @return bool success
224  */
225  public function StartTLS() {
226  $this->error = null; # to avoid confusion
227 
228  if(!$this->connected()) {
229  $this->error = array('error' => 'Called StartTLS() without being connected');
230  return false;
231  }
232 
233  $this->client_send('STARTTLS' . $this->CRLF);
234 
235  $rply = $this->get_lines();
236  $code = substr($rply, 0, 3);
237 
238  if($this->do_debug >= 2) {
239  $this->edebug('SMTP -> FROM SERVER:' . $rply);
240  }
241 
242  if($code != 220) {
243  $this->error =
244  array('error' => 'STARTTLS not accepted from server',
245  'smtp_code' => $code,
246  'smtp_msg' => substr($rply, 4));
247  if($this->do_debug >= 1) {
248  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
249  }
250  return false;
251  }
252 
253  // Begin encrypted connection
254  if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
255  return false;
256  }
257 
258  return true;
259  }
260 
261  /**
262  * Performs SMTP authentication. Must be run after running the
263  * Hello() method. Returns true if successfully authenticated.
264  * @access public
265  * @param string $username
266  * @param string $password
267  * @param string $authtype
268  * @param string $realm
269  * @param string $workstation
270  * @return bool
271  */
272  public function Authenticate($username, $password, $authtype='LOGIN', $realm='', $workstation='') {
273  if (empty($authtype)) {
274  $authtype = 'LOGIN';
275  }
276 
277  switch ($authtype) {
278  case 'PLAIN':
279  // Start authentication
280  $this->client_send('AUTH PLAIN' . $this->CRLF);
281 
282  $rply = $this->get_lines();
283  $code = substr($rply, 0, 3);
284 
285  if($code != 334) {
286  $this->error =
287  array('error' => 'AUTH not accepted from server',
288  'smtp_code' => $code,
289  'smtp_msg' => substr($rply, 4));
290  if($this->do_debug >= 1) {
291  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
292  }
293  return false;
294  }
295  // Send encoded username and password
296  $this->client_send(base64_encode("\0".$username."\0".$password) . $this->CRLF);
297 
298  $rply = $this->get_lines();
299  $code = substr($rply, 0, 3);
300 
301  if($code != 235) {
302  $this->error =
303  array('error' => 'Authentication not accepted from server',
304  'smtp_code' => $code,
305  'smtp_msg' => substr($rply, 4));
306  if($this->do_debug >= 1) {
307  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
308  }
309  return false;
310  }
311  break;
312  case 'LOGIN':
313  // Start authentication
314  $this->client_send('AUTH LOGIN' . $this->CRLF);
315 
316  $rply = $this->get_lines();
317  $code = substr($rply, 0, 3);
318 
319  if($code != 334) {
320  $this->error =
321  array('error' => 'AUTH not accepted from server',
322  'smtp_code' => $code,
323  'smtp_msg' => substr($rply, 4));
324  if($this->do_debug >= 1) {
325  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
326  }
327  return false;
328  }
329 
330  // Send encoded username
331  $this->client_send(base64_encode($username) . $this->CRLF);
332 
333  $rply = $this->get_lines();
334  $code = substr($rply, 0, 3);
335 
336  if($code != 334) {
337  $this->error =
338  array('error' => 'Username not accepted from server',
339  'smtp_code' => $code,
340  'smtp_msg' => substr($rply, 4));
341  if($this->do_debug >= 1) {
342  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
343  }
344  return false;
345  }
346 
347  // Send encoded password
348  $this->client_send(base64_encode($password) . $this->CRLF);
349 
350  $rply = $this->get_lines();
351  $code = substr($rply, 0, 3);
352 
353  if($code != 235) {
354  $this->error =
355  array('error' => 'Password not accepted from server',
356  'smtp_code' => $code,
357  'smtp_msg' => substr($rply, 4));
358  if($this->do_debug >= 1) {
359  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
360  }
361  return false;
362  }
363  break;
364  case 'NTLM':
365  /*
366  * ntlm_sasl_client.php
367  ** Bundled with Permission
368  **
369  ** How to telnet in windows: http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
370  ** PROTOCOL Documentation http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
371  */
372  if (file_exists('extras/ntlm_sasl_client.php')) {
373  require_once 'extras/ntlm_sasl_client.php';
374  }
375  $temp = new stdClass();
376  $ntlm_client = new ntlm_sasl_client_class;
377  if(! $ntlm_client->Initialize($temp)){//let's test if every function its available
378  $this->error = array('error' => $temp->error);
379  if($this->do_debug >= 1) {
380  $this->edebug('You need to enable some modules in your php.ini file: ' . $this->error['error']);
381  }
382  return false;
383  }
384  $msg1 = $ntlm_client->TypeMsg1($realm, $workstation);//msg1
385 
386  $this->client_send('AUTH NTLM ' . base64_encode($msg1) . $this->CRLF);
387 
388  $rply = $this->get_lines();
389  $code = substr($rply, 0, 3);
390 
391  if($code != 334) {
392  $this->error =
393  array('error' => 'AUTH not accepted from server',
394  'smtp_code' => $code,
395  'smtp_msg' => substr($rply, 4));
396  if($this->do_debug >= 1) {
397  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
398  }
399  return false;
400  }
401 
402  $challenge = substr($rply, 3);//though 0 based, there is a white space after the 3 digit number....//msg2
403  $challenge = base64_decode($challenge);
404  $ntlm_res = $ntlm_client->NTLMResponse(substr($challenge, 24, 8), $password);
405  $msg3 = $ntlm_client->TypeMsg3($ntlm_res, $username, $realm, $workstation);//msg3
406  // Send encoded username
407  $this->client_send(base64_encode($msg3) . $this->CRLF);
408 
409  $rply = $this->get_lines();
410  $code = substr($rply, 0, 3);
411 
412  if($code != 235) {
413  $this->error =
414  array('error' => 'Could not authenticate',
415  'smtp_code' => $code,
416  'smtp_msg' => substr($rply, 4));
417  if($this->do_debug >= 1) {
418  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
419  }
420  return false;
421  }
422  break;
423  case 'CRAM-MD5':
424  // Start authentication
425  $this->client_send('AUTH CRAM-MD5' . $this->CRLF);
426 
427  $rply = $this->get_lines();
428  $code = substr($rply, 0, 3);
429 
430  if($code != 334) {
431  $this->error =
432  array('error' => 'AUTH not accepted from server',
433  'smtp_code' => $code,
434  'smtp_msg' => substr($rply, 4));
435  if($this->do_debug >= 1) {
436  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
437  }
438  return false;
439  }
440 
441  // Get the challenge
442  $challenge = base64_decode(substr($rply, 4));
443 
444  // Build the response
445  $response = $username . ' ' . $this->hmac($challenge, $password);
446 
447  // Send encoded credentials
448  $this->client_send(base64_encode($response) . $this->CRLF);
449 
450  $rply = $this->get_lines();
451  $code = substr($rply, 0, 3);
452 
453  if($code != 235) {
454  $this->error =
455  array('error' => 'Credentials not accepted from server',
456  'smtp_code' => $code,
457  'smtp_msg' => substr($rply, 4));
458  if($this->do_debug >= 1) {
459  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
460  }
461  return false;
462  }
463  break;
464  }
465  return true;
466  }
467 
468  /**
469  * Works like hash_hmac('md5', $data, $key) in case that function is not available
470  * @access protected
471  * @param string $data
472  * @param string $key
473  * @return string
474  */
475  protected function hmac($data, $key) {
476  if (function_exists('hash_hmac')) {
477  return hash_hmac('md5', $data, $key);
478  }
479 
480  // The following borrowed from http://php.net/manual/en/function.mhash.php#27225
481 
482  // RFC 2104 HMAC implementation for php.
483  // Creates an md5 HMAC.
484  // Eliminates the need to install mhash to compute a HMAC
485  // Hacked by Lance Rushing
486 
487  $b = 64; // byte length for md5
488  if (strlen($key) > $b) {
489  $key = pack('H*', md5($key));
490  }
491  $key = str_pad($key, $b, chr(0x00));
492  $ipad = str_pad('', $b, chr(0x36));
493  $opad = str_pad('', $b, chr(0x5c));
494  $k_ipad = $key ^ $ipad ;
495  $k_opad = $key ^ $opad;
496 
497  return md5($k_opad . pack('H*', md5($k_ipad . $data)));
498  }
499 
500  /**
501  * Returns true if connected to a server otherwise false
502  * @access public
503  * @return bool
504  */
505  public function Connected() {
506  if(!empty($this->smtp_conn)) {
507  $sock_status = stream_get_meta_data($this->smtp_conn);
508  if($sock_status['eof']) {
509  // the socket is valid but we are not connected
510  if($this->do_debug >= 1) {
511  $this->edebug('SMTP -> NOTICE: EOF caught while checking if connected');
512  }
513  $this->Close();
514  return false;
515  }
516  return true; // everything looks good
517  }
518  return false;
519  }
520 
521  /**
522  * Closes the socket and cleans up the state of the class.
523  * It is not considered good to use this function without
524  * first trying to use QUIT.
525  * @access public
526  * @return void
527  */
528  public function Close() {
529  $this->error = null; // so there is no confusion
530  $this->helo_rply = null;
531  if(!empty($this->smtp_conn)) {
532  // close the connection and cleanup
533  fclose($this->smtp_conn);
534  $this->smtp_conn = 0;
535  }
536  }
537 
538  /////////////////////////////////////////////////
539  // SMTP COMMANDS
540  /////////////////////////////////////////////////
541 
542  /**
543  * Issues a data command and sends the msg_data to the server
544  * finializing the mail transaction. $msg_data is the message
545  * that is to be send with the headers. Each header needs to be
546  * on a single line followed by a <CRLF> with the message headers
547  * and the message body being separated by and additional <CRLF>.
548  *
549  * Implements rfc 821: DATA <CRLF>
550  *
551  * SMTP CODE INTERMEDIATE: 354
552  * [data]
553  * <CRLF>.<CRLF>
554  * SMTP CODE SUCCESS: 250
555  * SMTP CODE FAILURE: 552, 554, 451, 452
556  * SMTP CODE FAILURE: 451, 554
557  * SMTP CODE ERROR : 500, 501, 503, 421
558  * @access public
559  * @param string $msg_data
560  * @return bool
561  */
562  public function Data($msg_data) {
563  $this->error = null; // so no confusion is caused
564 
565  if(!$this->connected()) {
566  $this->error = array(
567  'error' => 'Called Data() without being connected');
568  return false;
569  }
570 
571  $this->client_send('DATA' . $this->CRLF);
572 
573  $rply = $this->get_lines();
574  $code = substr($rply, 0, 3);
575 
576  if($this->do_debug >= 2) {
577  $this->edebug('SMTP -> FROM SERVER:' . $rply);
578  }
579 
580  if($code != 354) {
581  $this->error =
582  array('error' => 'DATA command not accepted from server',
583  'smtp_code' => $code,
584  'smtp_msg' => substr($rply, 4));
585  if($this->do_debug >= 1) {
586  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
587  }
588  return false;
589  }
590 
591  /* the server is ready to accept data!
592  * according to rfc 821 we should not send more than 1000
593  * including the CRLF
594  * characters on a single line so we will break the data up
595  * into lines by \r and/or \n then if needed we will break
596  * each of those into smaller lines to fit within the limit.
597  * in addition we will be looking for lines that start with
598  * a period '.' and append and additional period '.' to that
599  * line. NOTE: this does not count towards limit.
600  */
601 
602  // normalize the line breaks so we know the explode works
603  $msg_data = str_replace("\r\n", "\n", $msg_data);
604  $msg_data = str_replace("\r", "\n", $msg_data);
605  $lines = explode("\n", $msg_data);
606 
607  /* we need to find a good way to determine is headers are
608  * in the msg_data or if it is a straight msg body
609  * currently I am assuming rfc 822 definitions of msg headers
610  * and if the first field of the first line (':' sperated)
611  * does not contain a space then it _should_ be a header
612  * and we can process all lines before a blank "" line as
613  * headers.
614  */
615 
616  $field = substr($lines[0], 0, strpos($lines[0], ':'));
617  $in_headers = false;
618  if(!empty($field) && !strstr($field, ' ')) {
619  $in_headers = true;
620  }
621 
622  $max_line_length = 998; // used below; set here for ease in change
623 
624  while(list(, $line) = @each($lines)) {
625  $lines_out = null;
626  if($line == '' && $in_headers) {
627  $in_headers = false;
628  }
629  // ok we need to break this line up into several smaller lines
630  while(strlen($line) > $max_line_length) {
631  $pos = strrpos(substr($line, 0, $max_line_length), ' ');
632 
633  // Patch to fix DOS attack
634  if(!$pos) {
635  $pos = $max_line_length - 1;
636  $lines_out[] = substr($line, 0, $pos);
637  $line = substr($line, $pos);
638  } else {
639  $lines_out[] = substr($line, 0, $pos);
640  $line = substr($line, $pos + 1);
641  }
642 
643  /* if processing headers add a LWSP-char to the front of new line
644  * rfc 822 on long msg headers
645  */
646  if($in_headers) {
647  $line = "\t" . $line;
648  }
649  }
650  $lines_out[] = $line;
651 
652  // send the lines to the server
653  while(list(, $line_out) = @each($lines_out)) {
654  if(strlen($line_out) > 0)
655  {
656  if(substr($line_out, 0, 1) == '.') {
657  $line_out = '.' . $line_out;
658  }
659  }
660  $this->client_send($line_out . $this->CRLF);
661  }
662  }
663 
664  // message data has been sent
665  $this->client_send($this->CRLF . '.' . $this->CRLF);
666 
667  $rply = $this->get_lines();
668  $code = substr($rply, 0, 3);
669 
670  if($this->do_debug >= 2) {
671  $this->edebug('SMTP -> FROM SERVER:' . $rply);
672  }
673 
674  if($code != 250) {
675  $this->error =
676  array('error' => 'DATA not accepted from server',
677  'smtp_code' => $code,
678  'smtp_msg' => substr($rply, 4));
679  if($this->do_debug >= 1) {
680  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
681  }
682  return false;
683  }
684  return true;
685  }
686 
687  /**
688  * Sends the HELO command to the smtp server.
689  * This makes sure that we and the server are in
690  * the same known state.
691  *
692  * Implements from rfc 821: HELO <SP> <domain> <CRLF>
693  *
694  * SMTP CODE SUCCESS: 250
695  * SMTP CODE ERROR : 500, 501, 504, 421
696  * @access public
697  * @param string $host
698  * @return bool
699  */
700  public function Hello($host = '') {
701  $this->error = null; // so no confusion is caused
702 
703  if(!$this->connected()) {
704  $this->error = array(
705  'error' => 'Called Hello() without being connected');
706  return false;
707  }
708 
709  // if hostname for HELO was not specified send default
710  if(empty($host)) {
711  // determine appropriate default to send to server
712  $host = 'localhost';
713  }
714 
715  // Send extended hello first (RFC 2821)
716  if(!$this->SendHello('EHLO', $host)) {
717  if(!$this->SendHello('HELO', $host)) {
718  return false;
719  }
720  }
721 
722  return true;
723  }
724 
725  /**
726  * Sends a HELO/EHLO command.
727  * @access protected
728  * @param string $hello
729  * @param string $host
730  * @return bool
731  */
732  protected function SendHello($hello, $host) {
733  $this->client_send($hello . ' ' . $host . $this->CRLF);
734 
735  $rply = $this->get_lines();
736  $code = substr($rply, 0, 3);
737 
738  if($this->do_debug >= 2) {
739  $this->edebug('SMTP -> FROM SERVER: ' . $rply);
740  }
741 
742  if($code != 250) {
743  $this->error =
744  array('error' => $hello . ' not accepted from server',
745  'smtp_code' => $code,
746  'smtp_msg' => substr($rply, 4));
747  if($this->do_debug >= 1) {
748  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
749  }
750  return false;
751  }
752 
753  $this->helo_rply = $rply;
754 
755  return true;
756  }
757 
758  /**
759  * Starts a mail transaction from the email address specified in
760  * $from. Returns true if successful or false otherwise. If True
761  * the mail transaction is started and then one or more Recipient
762  * commands may be called followed by a Data command.
763  *
764  * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
765  *
766  * SMTP CODE SUCCESS: 250
767  * SMTP CODE SUCCESS: 552, 451, 452
768  * SMTP CODE SUCCESS: 500, 501, 421
769  * @access public
770  * @param string $from
771  * @return bool
772  */
773  public function Mail($from) {
774  $this->error = null; // so no confusion is caused
775 
776  if(!$this->connected()) {
777  $this->error = array(
778  'error' => 'Called Mail() without being connected');
779  return false;
780  }
781 
782  $useVerp = ($this->do_verp ? ' XVERP' : '');
783  $this->client_send('MAIL FROM:<' . $from . '>' . $useVerp . $this->CRLF);
784 
785  $rply = $this->get_lines();
786  $code = substr($rply, 0, 3);
787 
788  if($this->do_debug >= 2) {
789  $this->edebug('SMTP -> FROM SERVER:' . $rply);
790  }
791 
792  if($code != 250) {
793  $this->error =
794  array('error' => 'MAIL not accepted from server',
795  'smtp_code' => $code,
796  'smtp_msg' => substr($rply, 4));
797  if($this->do_debug >= 1) {
798  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
799  }
800  return false;
801  }
802  return true;
803  }
804 
805  /**
806  * Sends the quit command to the server and then closes the socket
807  * if there is no error or the $close_on_error argument is true.
808  *
809  * Implements from rfc 821: QUIT <CRLF>
810  *
811  * SMTP CODE SUCCESS: 221
812  * SMTP CODE ERROR : 500
813  * @access public
814  * @param bool $close_on_error
815  * @return bool
816  */
817  public function Quit($close_on_error = true) {
818  $this->error = null; // so there is no confusion
819 
820  if(!$this->connected()) {
821  $this->error = array(
822  'error' => 'Called Quit() without being connected');
823  return false;
824  }
825 
826  // send the quit command to the server
827  $this->client_send('quit' . $this->CRLF);
828 
829  // get any good-bye messages
830  $byemsg = $this->get_lines();
831 
832  if($this->do_debug >= 2) {
833  $this->edebug('SMTP -> FROM SERVER:' . $byemsg);
834  }
835 
836  $rval = true;
837  $e = null;
838 
839  $code = substr($byemsg, 0, 3);
840  if($code != 221) {
841  // use e as a tmp var cause Close will overwrite $this->error
842  $e = array('error' => 'SMTP server rejected quit command',
843  'smtp_code' => $code,
844  'smtp_rply' => substr($byemsg, 4));
845  $rval = false;
846  if($this->do_debug >= 1) {
847  $this->edebug('SMTP -> ERROR: ' . $e['error'] . ': ' . $byemsg);
848  }
849  }
850 
851  if(empty($e) || $close_on_error) {
852  $this->Close();
853  }
854 
855  return $rval;
856  }
857 
858  /**
859  * Sends the command RCPT to the SMTP server with the TO: argument of $to.
860  * Returns true if the recipient was accepted false if it was rejected.
861  *
862  * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
863  *
864  * SMTP CODE SUCCESS: 250, 251
865  * SMTP CODE FAILURE: 550, 551, 552, 553, 450, 451, 452
866  * SMTP CODE ERROR : 500, 501, 503, 421
867  * @access public
868  * @param string $to
869  * @return bool
870  */
871  public function Recipient($to) {
872  $this->error = null; // so no confusion is caused
873 
874  if(!$this->connected()) {
875  $this->error = array(
876  'error' => 'Called Recipient() without being connected');
877  return false;
878  }
879 
880  $this->client_send('RCPT TO:<' . $to . '>' . $this->CRLF);
881 
882  $rply = $this->get_lines();
883  $code = substr($rply, 0, 3);
884 
885  if($this->do_debug >= 2) {
886  $this->edebug('SMTP -> FROM SERVER:' . $rply);
887  }
888 
889  if($code != 250 && $code != 251) {
890  $this->error =
891  array('error' => 'RCPT not accepted from server',
892  'smtp_code' => $code,
893  'smtp_msg' => substr($rply, 4));
894  if($this->do_debug >= 1) {
895  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
896  }
897  return false;
898  }
899  return true;
900  }
901 
902  /**
903  * Sends the RSET command to abort and transaction that is
904  * currently in progress. Returns true if successful false
905  * otherwise.
906  *
907  * Implements rfc 821: RSET <CRLF>
908  *
909  * SMTP CODE SUCCESS: 250
910  * SMTP CODE ERROR : 500, 501, 504, 421
911  * @access public
912  * @return bool
913  */
914  public function Reset() {
915  $this->error = null; // so no confusion is caused
916 
917  if(!$this->connected()) {
918  $this->error = array('error' => 'Called Reset() without being connected');
919  return false;
920  }
921 
922  $this->client_send('RSET' . $this->CRLF);
923 
924  $rply = $this->get_lines();
925  $code = substr($rply, 0, 3);
926 
927  if($this->do_debug >= 2) {
928  $this->edebug('SMTP -> FROM SERVER:' . $rply);
929  }
930 
931  if($code != 250) {
932  $this->error =
933  array('error' => 'RSET failed',
934  'smtp_code' => $code,
935  'smtp_msg' => substr($rply, 4));
936  if($this->do_debug >= 1) {
937  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
938  }
939  return false;
940  }
941 
942  return true;
943  }
944 
945  /**
946  * Starts a mail transaction from the email address specified in
947  * $from. Returns true if successful or false otherwise. If True
948  * the mail transaction is started and then one or more Recipient
949  * commands may be called followed by a Data command. This command
950  * will send the message to the users terminal if they are logged
951  * in and send them an email.
952  *
953  * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
954  *
955  * SMTP CODE SUCCESS: 250
956  * SMTP CODE SUCCESS: 552, 451, 452
957  * SMTP CODE SUCCESS: 500, 501, 502, 421
958  * @access public
959  * @param string $from
960  * @return bool
961  */
962  public function SendAndMail($from) {
963  $this->error = null; // so no confusion is caused
964 
965  if(!$this->connected()) {
966  $this->error = array(
967  'error' => 'Called SendAndMail() without being connected');
968  return false;
969  }
970 
971  $this->client_send('SAML FROM:' . $from . $this->CRLF);
972 
973  $rply = $this->get_lines();
974  $code = substr($rply, 0, 3);
975 
976  if($this->do_debug >= 2) {
977  $this->edebug('SMTP -> FROM SERVER:' . $rply);
978  }
979 
980  if($code != 250) {
981  $this->error =
982  array('error' => 'SAML not accepted from server',
983  'smtp_code' => $code,
984  'smtp_msg' => substr($rply, 4));
985  if($this->do_debug >= 1) {
986  $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply);
987  }
988  return false;
989  }
990  return true;
991  }
992 
993  /**
994  * This is an optional command for SMTP that this class does not
995  * support. This method is here to make the RFC821 Definition
996  * complete for this class and __may__ be implimented in the future
997  *
998  * Implements from rfc 821: TURN <CRLF>
999  *
1000  * SMTP CODE SUCCESS: 250
1001  * SMTP CODE FAILURE: 502
1002  * SMTP CODE ERROR : 500, 503
1003  * @access public
1004  * @return bool
1005  */
1006  public function Turn() {
1007  $this->error = array('error' => 'This method, TURN, of the SMTP '.
1008  'is not implemented');
1009  if($this->do_debug >= 1) {
1010  $this->edebug('SMTP -> NOTICE: ' . $this->error['error']);
1011  }
1012  return false;
1013  }
1014 
1015  /**
1016  * Sends data to the server
1017  * @param string $data
1018  * @access public
1019  * @return Integer number of bytes sent to the server or FALSE on error
1020  */
1021  public function client_send($data) {
1022  if ($this->do_debug >= 1) {
1023  $this->edebug("CLIENT -> SMTP: $data");
1024  }
1025  return fwrite($this->smtp_conn, $data);
1026  }
1027 
1028  /**
1029  * Get the current error
1030  * @access public
1031  * @return array
1032  */
1033  public function getError() {
1034  return $this->error;
1035  }
1036 
1037  /////////////////////////////////////////////////
1038  // INTERNAL FUNCTIONS
1039  /////////////////////////////////////////////////
1040 
1041  /**
1042  * Read in as many lines as possible
1043  * either before eof or socket timeout occurs on the operation.
1044  * With SMTP we can tell if we have more lines to read if the
1045  * 4th character is '-' symbol. If it is a space then we don't
1046  * need to read anything else.
1047  * @access protected
1048  * @return string
1049  */
1050  protected function get_lines() {
1051  $data = '';
1052  $endtime = 0;
1053  /* If for some reason the fp is bad, don't inf loop */
1054  if (!is_resource($this->smtp_conn)) {
1055  return $data;
1056  }
1057  stream_set_timeout($this->smtp_conn, $this->Timeout);
1058  if ($this->Timelimit > 0) {
1059  $endtime = time() + $this->Timelimit;
1060  }
1061  while(is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1062  $str = @fgets($this->smtp_conn, 515);
1063  if($this->do_debug >= 4) {
1064  $this->edebug("SMTP -> get_lines(): \$data was \"$data\"");
1065  $this->edebug("SMTP -> get_lines(): \$str is \"$str\"");
1066  }
1067  $data .= $str;
1068  if($this->do_debug >= 4) {
1069  $this->edebug("SMTP -> get_lines(): \$data is \"$data\"");
1070  }
1071  // if 4th character is a space, we are done reading, break the loop
1072  if(substr($str, 3, 1) == ' ') { break; }
1073  // Timed-out? Log and break
1074  $info = stream_get_meta_data($this->smtp_conn);
1075  if ($info['timed_out']) {
1076  if($this->do_debug >= 4) {
1077  $this->edebug('SMTP -> get_lines(): timed-out (' . $this->Timeout . ' seconds)');
1078  }
1079  break;
1080  }
1081  // Now check if reads took too long
1082  if ($endtime) {
1083  if (time() > $endtime) {
1084  if($this->do_debug >= 4) {
1085  $this->edebug('SMTP -> get_lines(): timelimit reached (' . $this->Timelimit . ' seconds)');
1086  }
1087  break;
1088  }
1089  }
1090  }
1091  return $data;
1092  }
1093 
1094 }