root/tags/1.3/wp-includes/class-smtp.php

Revision 972, 32.7 kB (checked in by donncha, 2 years ago)

WP Merge to rev 5499, this is a big one! Test it before you put it live!
Test only, not for production use yet

  • Property svn:eol-style set to native
Line 
1 <?php
2 ////////////////////////////////////////////////////
3 // SMTP - PHP SMTP class
4 //
5 // Version 1.02
6 //
7 // Define an SMTP class that can be used to connect
8 // and communicate with any SMTP server. It implements
9 // all the SMTP functions defined in RFC821 except TURN.
10 //
11 // Author: Chris Ryan
12 //
13 // License: LGPL, see LICENSE
14 ////////////////////////////////////////////////////
15
16 /**
17  * SMTP is rfc 821 compliant and implements all the rfc 821 SMTP
18  * commands except TURN which will always return a not implemented
19  * error. SMTP also provides some utility methods for sending mail
20  * to an SMTP server.
21  * @package PHPMailer
22  * @author Chris Ryan
23  */
24 class SMTP
25 {
26     /**
27      *  SMTP server port
28      *  @var int
29      */
30     var $SMTP_PORT = 25;
31     
32     /**
33      *  SMTP reply line ending
34      *  @var string
35      */
36     var $CRLF = "\r\n";
37     
38     /**
39      *  Sets whether debugging is turned on
40      *  @var bool
41      */
42     var $do_debug;       # the level of debug to perform
43
44     /**#@+
45      * @access private
46      */
47     var $smtp_conn;      # the socket to the server
48     var $error;          # error if any on the last call
49     var $helo_rply;      # the reply the server sent to us for HELO
50     /**#@-*/
51
52     /**
53      * Initialize the class so that the data is in a known state.
54      * @access public
55      * @return void
56      */
57     function SMTP() {
58         $this->smtp_conn = 0;
59         $this->error = null;
60         $this->helo_rply = null;
61
62         $this->do_debug = 0;
63     }
64
65     /*************************************************************
66      *                    CONNECTION FUNCTIONS                  *
67      ***********************************************************/
68
69     /**
70      * Connect to the server specified on the port specified.
71      * If the port is not specified use the default SMTP_PORT.
72      * If tval is specified then a connection will try and be
73      * established with the server for that number of seconds.
74      * If tval is not specified the default is 30 seconds to
75      * try on the connection.
76      *
77      * SMTP CODE SUCCESS: 220
78      * SMTP CODE FAILURE: 421
79      * @access public
80      * @return bool
81      */
82     function Connect($host,$port=0,$tval=30) {
83         # set the error val to null so there is no confusion
84         $this->error = null;
85
86         # make sure we are __not__ connected
87         if($this->connected()) {
88             # ok we are connected! what should we do?
89             # for now we will just give an error saying we
90             # are already connected
91             $this->error =
92                 array("error" => "Already connected to a server");
93             return false;
94         }
95
96         if(empty($port)) {
97             $port = $this->SMTP_PORT;
98         }
99
100         #connect to the smtp server
101         $this->smtp_conn = fsockopen($host,    # the host of the server
102                                      $port,    # the port to use
103                                      $errno,   # error number if any
104                                      $errstr# error message if any
105                                      $tval);   # give up after ? secs
106         # verify we connected properly
107         if(empty($this->smtp_conn)) {
108             $this->error = array("error" => "Failed to connect to server",
109                                  "errno" => $errno,
110                                  "errstr" => $errstr);
111             if($this->do_debug >= 1) {
112                 echo "SMTP -> ERROR: " . $this->error["error"] .
113                          ": $errstr ($errno)" . $this->CRLF;
114             }
115             return false;
116         }
117
118         # sometimes the SMTP server takes a little longer to respond
119         # so we will give it a longer timeout for the first read
120         // Windows still does not have support for this timeout function
121         if(substr(PHP_OS, 0, 3) != "WIN")
122            socket_set_timeout($this->smtp_conn, $tval, 0);
123
124         # get any announcement stuff
125         $announce = $this->get_lines();
126
127         # set the timeout  of any socket functions at 1/10 of a second
128         //if(function_exists("socket_set_timeout"))
129         //   socket_set_timeout($this->smtp_conn, 0, 100000);
130
131         if($this->do_debug >= 2) {
132             echo "SMTP -> FROM SERVER:" . $this->CRLF . $announce;
133         }
134
135         return true;
136     }
137
138     /**
139      * Performs SMTP authentication.  Must be run after running the
140      * Hello() method.  Returns true if successfully authenticated.
141      * @access public
142      * @return bool
143      */
144     function Authenticate($username, $password) {
145         // Start authentication
146         fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF);
147
148         $rply = $this->get_lines();
149         $code = substr($rply,0,3);
150
151         if($code != 334) {
152             $this->error =
153                 array("error" => "AUTH not accepted from server",
154                       "smtp_code" => $code,
155                       "smtp_msg" => substr($rply,4));
156             if($this->do_debug >= 1) {
157                 echo "SMTP -> ERROR: " . $this->error["error"] .
158                          ": " . $rply . $this->CRLF;
159             }
160             return false;
161         }
162
163         // Send encoded username
164         fputs($this->smtp_conn, base64_encode($username) . $this->CRLF);
165
166         $rply = $this->get_lines();
167         $code = substr($rply,0,3);
168
169         if($code != 334) {
170             $this->error =
171                 array("error" => "Username not accepted from server",
172                       "smtp_code" => $code,
173                       "smtp_msg" => substr($rply,4));
174             if($this->do_debug >= 1) {
175                 echo "SMTP -> ERROR: " . $this->error["error"] .
176                          ": " . $rply . $this->CRLF;
177             }
178             return false;
179         }
180
181         // Send encoded password
182         fputs($this->smtp_conn, base64_encode($password) . $this->CRLF);
183
184         $rply = $this->get_lines();
185         $code = substr($rply,0,3);
186
187         if($code != 235) {
188             $this->error =
189                 array("error" => "Password not accepted from server",
190                       "smtp_code" => $code,
191                       "smtp_msg" => substr($rply,4));
192             if($this->do_debug >= 1) {
193                 echo "SMTP -> ERROR: " . $this->error["error"] .
194                          ": " . $rply . $this->CRLF;
195             }
196             return false;
197         }
198
199         return true;
200     }
201
202     /**
203      * Returns true if connected to a server otherwise false
204      * @access private
205      * @return bool
206      */
207     function Connected() {
208         if(!empty($this->smtp_conn)) {
209             $sock_status = socket_get_status($this->smtp_conn);
210             if($sock_status["eof"]) {
211                 # hmm this is an odd situation... the socket is
212                 # valid but we aren't connected anymore
213                 if($this->do_debug >= 1) {
214                     echo "SMTP -> NOTICE:" . $this->CRLF .
215                          "EOF caught while checking if connected";
216                 }
217                 $this->Close();
218                 return false;
219             }
220             return true; # everything looks good
221         }
222         return false;
223     }
224
225     /**
226      * Closes the socket and cleans up the state of the class.
227      * It is not considered good to use this function without
228      * first trying to use QUIT.
229      * @access public
230      * @return void
231      */
232     function Close() {
233         $this->error = null; # so there is no confusion
234         $this->helo_rply = null;
235         if(!empty($this->smtp_conn)) {
236             # close the connection and cleanup
237             fclose($this->smtp_conn);
238             $this->smtp_conn = 0;
239         }
240     }
241
242
243     /***************************************************************
244      *                        SMTP COMMANDS                       *
245      *************************************************************/
246
247     /**
248      * Issues a data command and sends the msg_data to the server
249      * finializing the mail transaction. $msg_data is the message
250      * that is to be send with the headers. Each header needs to be
251      * on a single line followed by a <CRLF> with the message headers
252      * and the message body being seperated by and additional <CRLF>.
253      *
254      * Implements rfc 821: DATA <CRLF>
255      *
256      * SMTP CODE INTERMEDIATE: 354
257      *     [data]
258      *     <CRLF>.<CRLF>
259      *     SMTP CODE SUCCESS: 250
260      *     SMTP CODE FAILURE: 552,554,451,452
261      * SMTP CODE FAILURE: 451,554
262      * SMTP CODE ERROR  : 500,501,503,421
263      * @access public
264      * @return bool
265      */
266     function Data($msg_data) {
267         $this->error = null; # so no confusion is caused
268
269         if(!$this->connected()) {
270             $this->error = array(
271                     "error" => "Called Data() without being connected");
272             return false;
273         }
274
275         fputs($this->smtp_conn,"DATA" . $this->CRLF);
276
277         $rply = $this->get_lines();
278         $code = substr($rply,0,3);
279
280         if($this->do_debug >= 2) {
281             echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
282         }
283
284         if($code != 354) {
285             $this->error =
286                 array("error" => "DATA command not accepted from server",
287                       "smtp_code" => $code,
288                       "smtp_msg" => substr($rply,4));
289             if($this->do_debug >= 1) {
290                 echo "SMTP -> ERROR: " . $this->error["error"] .
291                          ": " . $rply . $this->CRLF;
292             }
293             return false;
294         }
295
296         # the server is ready to accept data!
297         # according to rfc 821 we should not send more than 1000
298         # including the CRLF
299         # characters on a single line so we will break the data up
300         # into lines by \r and/or \n then if needed we will break
301         # each of those into smaller lines to fit within the limit.
302         # in addition we will be looking for lines that start with
303         # a period '.' and append and additional period '.' to that
304         # line. NOTE: this does not count towards are limit.
305
306         # normalize the line breaks so we know the explode works
307         $msg_data = str_replace("\r\n","\n",$msg_data);
308         $msg_data = str_replace("\r","\n",$msg_data);
309         $lines = explode("\n",$msg_data);
310
311         # we need to find a good way to determine is headers are
312         # in the msg_data or if it is a straight msg body
313         # currently I'm assuming rfc 822 definitions of msg headers
314         # and if the first field of the first line (':' sperated)
315         # does not contain a space then it _should_ be a header
316         # and we can process all lines before a blank "" line as
317         # headers.
318         $field = substr($lines[0],0,strpos($lines[0],":"));
319         $in_headers = false;
320         if(!empty($field) && !strstr($field," ")) {
321             $in_headers = true;
322         }
323
324         $max_line_length = 998; # used below; set here for ease in change
325
326         while(list(,$line) = @each($lines)) {
327             $lines_out = null;
328             if($line == "" && $in_headers) {
329                 $in_headers = false;
330             }
331             # ok we need to break this line up into several
332             # smaller lines
333             while(strlen($line) > $max_line_length) {
334                 $pos = strrpos(substr($line,0,$max_line_length)," ");
335
336                 # Patch to fix DOS attack
337                 if(!$pos) {
338                     $pos = $max_line_length - 1;
339                 }
340
341                 $lines_out[] = substr($line,0,$pos);
342                 $line = substr($line,$pos + 1);
343                 # if we are processing headers we need to
344                 # add a LWSP-char to the front of the new line
345                 # rfc 822 on long msg headers
346                 if($in_headers) {
347                     $line = "\t" . $line;
348                 }
349             }
350             $lines_out[] = $line;
351
352             # now send the lines to the server
353             while(list(,$line_out) = @each($lines_out)) {
354                 if(strlen($line_out) > 0)
355                 {
356                     if(substr($line_out, 0, 1) == ".") {
357                         $line_out = "." . $line_out;
358                     }
359                 }
360                 fputs($this->smtp_conn,$line_out . $this->CRLF);
361             }
362         }
363
364         # ok all the message data has been sent so lets get this
365         # over with aleady
366         fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF);
367
368         $rply = $this->get_lines();
369         $code = substr($rply,0,3);
370
371         if($this->do_debug >= 2) {
372             echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
373         }
374
375         if($code != 250) {
376             $this->error =
377                 array("error" => "DATA not accepted from server",
378                       "smtp_code" => $code,
379                       "smtp_msg" => substr($rply,4));
380             if($this->do_debug >= 1) {
381                 echo "SMTP -> ERROR: " . $this->error["error"] .
382                          ": " . $rply . $this->CRLF;
383             }
384             return false;
385         }
386         return true;
387     }
388
389     /**
390      * Expand takes the name and asks the server to list all the
391      * people who are members of the _list_. Expand will return
392      * back and array of the result or false if an error occurs.
393      * Each value in the array returned has the format of:
394      *     [ <full-name> <sp> ] <path>
395      * The definition of <path> is defined in rfc 821
396      *
397      * Implements rfc 821: EXPN <SP> <string> <CRLF>
398      *
399      * SMTP CODE SUCCESS: 250
400      * SMTP CODE FAILURE: 550
401      * SMTP CODE ERROR  : 500,501,502,504,421
402      * @access public
403      * @return string array
404      */
405     function Expand($name) {
406         $this->error = null; # so no confusion is caused
407
408         if(!$this->connected()) {
409             $this->error = array(
410                     "error" => "Called Expand() without being connected");
411             return false;
412         }
413
414         fputs($this->smtp_conn,"EXPN " . $name . $this->CRLF);
415
416         $rply = $this->get_lines();
417         $code = substr($rply,0,3);
418
419         if($this->do_debug >= 2) {
420             echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
421         }
422
423         if($code != 250) {
424             $this->error =
425                 array("error" => "EXPN not accepted from server",
426                       "smtp_code" => $code,
427                       "smtp_msg" => substr($rply,4));
428             if($this->do_debug >= 1) {
429                 echo "SMTP -> ERROR: " . $this->error["error"] .
430                          ": " . $rply . $this->CRLF;
431             }
432             return false;
433         }
434
435         # parse the reply and place in our array to return to user
436         $entries = explode($this->CRLF,$rply);
437         while(list(,$l) = @each($entries)) {
438             $list[] = substr($l,4);
439         }
440
441         return $list;
442     }
443
444     /**
445      * Sends the HELO command to the smtp server.
446      * This makes sure that we and the server are in
447      * the same known state.
448      *
449      * Implements from rfc 821: HELO <SP> <domain> <CRLF>
450      *
451      * SMTP CODE SUCCESS: 250
452      * SMTP CODE ERROR  : 500, 501, 504, 421
453      * @access public
454      * @return bool
455      */
456     function Hello($host="") {
457         $this->error = null; # so no confusion is caused
458
459         if(!$this->connected()) {
460             $this->error = array(
461                     "error" => "Called Hello() without being connected");
462             return false;
463         }
464
465         # if a hostname for the HELO wasn't specified determine
466         # a suitable one to send
467         if(empty($host)) {
468             # we need to determine some sort of appopiate default
469             # to send to the server
470             $host = "localhost";
471         }
472
473         // Send extended hello first (RFC 2821)
474         if(!$this->SendHello("EHLO", $host))
475         {
476             if(!$this->SendHello("HELO", $host))
477                 return false;
478         }
479
480         return true;
481     }
482
483     /**
484      * Sends a HELO/EHLO command.
485      * @access private
486      * @return bool
487      */
488     function SendHello($hello, $host) {
489         fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF);
490
491         $rply = $this->get_lines();
492         $code = substr($rply,0,3);
493
494         if($this->do_debug >= 2) {
495             echo "SMTP -> FROM SERVER: " . $this->CRLF . $rply;
496         }
497
498         if($code != 250) {
499             $this->error =
500                 array("error" => $hello . " not accepted from server",
501                       "smtp_code" => $code,
502                       "smtp_msg" => substr($rply,4));
503             if($this->do_debug >= 1) {
504                 echo "SMTP -> ERROR: " . $this->error["error"] .
505                          ": " . $rply . $this->CRLF;
506             }
507             return false;
508         }
509
510         $this->helo_rply = $rply;
511         
512         return true;
513     }
514
515     /**
516      * Gets help information on the keyword specified. If the keyword
517      * is not specified then returns generic help, ussually contianing
518      * A list of keywords that help is available on. This function
519      * returns the results back to the user. It is up to the user to
520      * handle the returned data. If an error occurs then false is
521      * returned with $this->error set appropiately.
522      *
523      * Implements rfc 821: HELP [ <SP> <string> ] <CRLF>
524      *
525      * SMTP CODE SUCCESS: 211,214
526      * SMTP CODE ERROR  : 500,501,502,504,421
527      * @access public
528      * @return string
529      */
530     function Help($keyword="") {
531         $this->error = null; # to avoid confusion
532
533         if(!$this->connected()) {
534             $this->error = array(
535                     "error" => "Called Help() without being connected");
536             return false;
537         }
538
539         $extra = "";
540         if(!empty($keyword)) {
541             $extra = " " . $keyword;
542         }
543
544         fputs($this->smtp_conn,"HELP" . $extra