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