WordPress.org

Make WordPress Core

Ticket #21559: class-pop3.php

File class-pop3.php, 17.1 KB (added by bilalcoder, 21 months ago)
Line 
1<?php
2/**
3 * mail_fetch/setup.php
4 *
5 * Copyright (c) 1999-2011 CDI (cdi@thewebmasters.net) All Rights Reserved
6 * Modified by Philippe Mingo 2001-2009 mingo@rotedic.com
7 * An RFC 1939 compliant wrapper class for the POP3 protocol.
8 *
9 * Licensed under the GNU GPL. For full terms see the file COPYING.
10 *
11 * POP3 class
12 *
13 * @copyright 1999-2011 The SquirrelMail Project Team
14 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
15 * @package plugins
16 * @subpackage mail_fetch
17 */
18
19class POP3 {
20    var $ERROR      = '';       //  Error string.
21
22    var $TIMEOUT    = 60;       //  Default timeout before giving up on a
23                                //  network operation.
24
25    var $COUNT      = -1;       //  Mailbox msg count
26
27    var $BUFFER     = 512;      //  Socket buffer for socket fgets() calls.
28                                //  Per RFC 1939 the returned line a POP3
29                                //  server can send is 512 bytes.
30
31    var $FP         = '';       //  The connection to the server's
32                                //  file descriptor
33
34    var $MAILSERVER = '';       // Set this to hard code the server name
35    var $PORT           = 110;          // Server port
36
37    var $DEBUG      = FALSE;    // set to true to echo pop3
38                                // commands and responses to error_log
39                                // this WILL log passwords!
40
41    var $BANNER     = '';       //  Holds the banner returned by the
42                                //  pop server - used for apop()
43
44    var $ALLOWAPOP  = FALSE;    //  Allow or disallow apop()
45                                //  This must be set to true
46                                //  manually
47                               
48    var $safemode_enabled = true;
49
50    function POP3 ( $server = '', $timeout = 0, $port = 110, $connect = false ) {
51
52        $this->safemode_enabled = (bool)ini_get('safe_mode');
53        $this->BUFFER                   = (int)$this->BUFFER;
54       
55        $success = $this->_init( $server, $port, $timeout );
56       
57        if ($connect && $success) 
58                $this->connect( $this->MAILSERVER, $this->PORT );
59    }
60   
61    private function _init($server = '', $port = 110, $timeout = 0){
62
63        // Do not allow programs to alter MAILSERVER
64        // if it is already specified. They can get around
65        // this if they -really- want to, so don't count on it.
66        if (empty($this->MAILSERVER)) {
67                if (!empty($server))
68                        $this->MAILSERVER = $server;
69        }
70       
71        if ($timeout) {
72                $this->TIMEOUT = (int)$timeout;
73                if (!$this->safemode_enabled) 
74                        set_time_limit( $this->TIMEOUT );
75        }
76       
77        $port = (int)$port;
78        if ($port) {
79                $this->PORT = $port;
80        }
81       
82        return !empty($this->MAILSERVER);
83    }
84   
85    function set_error( $error , $caller = null, $close = false ) {
86        $this->ERROR = 'POP3 '. ( $caller ? $caller.'()' : '' ).': '. $error;
87       
88        if ($close)
89                unset($this->FP);
90       
91        return false;
92    }
93
94    function update_timer () {
95        if (!$this->safemode_enabled)
96            set_time_limit($this->TIMEOUT);
97        return true;
98    }
99
100    function connect ($server = '', $port = 110)  {
101       
102        if ( empty($this->MAILSERVER) || $this->PORT != $port ) {
103                if (!$this->_init( $server, $port )) {
104                        return $this->set_error( _("No server specified"), __FUNCTION__, true );
105                }
106        }
107               
108        $this->FP = @fsockopen($this->MAILSERVER, $this->PORT, $errno, $errstr);
109
110        if(!$this->FP) {
111            return $this->set_error(  _("Error ") . "[$errno] [$errstr]", __FUNCTION__, true );
112        }
113       
114                function_exists('stream_set_blocking') ? stream_set_blocking($this->FP, 1) : socket_set_blocking($this->FP, -1);
115
116        $this->update_timer();
117        $reply = $this->get_reply();
118       
119        if(!$this->is_ok($reply)) {
120            return $this->set_error( _("Error ") . "[$reply]", __FUNCTION__, true );
121        }
122       
123        $this->BANNER = $this->parse_banner($reply);
124        return true;
125    }
126   
127     private function send($string) {
128        $raw = $string;
129       
130        if ($this->FP) {
131                $string = strpos($string, "\r\n") === FALSE ? $string."\r\n" : $string;
132               
133                if ( ($number = @fwrite($this->FP, $string)) ) {
134                        if ($this->DEBUG) {
135                                @error_log('POP3 '. __FUNCTION__.'(): ['.$raw.']');
136                        }
137                        return $number;
138                }
139        }
140       
141        return false;
142    }
143   
144    private function get_reply($strip_clf = true) {
145        $reply = @fgets( $this->FP, $this->BUFFER );
146               
147        if ($reply) {
148                if ($strip_clf) {
149                        $reply = $this->strip_clf( $reply );
150                }
151               
152                if ($this->DEBUG) {
153                        @error_log('POP3 GOT: ['. $reply .'] FROM ['.$this->MAILSERVER.']',0);
154                }
155        }
156       
157        return $reply;
158    }
159
160    function user ($user = "") {
161        // Sends the USER command, returns true or false
162
163        if( empty($user) )
164            return $this->set_error( _("no login ID submitted"), __FUNCTION__ );
165       
166        if(!isset($this->FP))
167            return $this->set_error( _("connection not established"), __FUNCTION__ );
168       
169        $reply = $this->send_cmd("USER $user");
170        if(!$this->is_ok($reply))
171                return $this->set_error( _("Error ") . "[$reply]", __FUNCTION__ );
172               
173        return $this;
174    }
175
176    function pass ($pass = "")     {
177        // Sends the PASS command, returns # of msgs in mailbox,
178        // returns false (undef) on Auth failure
179
180        if(empty($pass))
181           return $this->set_error( _("No password submitted"), __FUNCTION__ );
182       
183        if(!isset($this->FP))
184            return $this->set_error( _("connection not established"), __FUNCTION__ );
185           
186        $reply = $this->send_cmd("PASS $pass");
187        if(!$this->is_ok($reply)) {
188            $this->set_error( _("Authentication failed") . " [$reply]", __FUNCTION__ );
189            $this->quit();
190            return false;
191        }
192       
193        //  Auth successful.
194        $this->COUNT = $this->last("count");
195        return $this->COUNT;
196    }
197
198    function apop ($login,$pass) {
199        //  Attempts an APOP login. If this fails, it'll
200        //  try a standard login. YOUR SERVER MUST SUPPORT
201        //  THE USE OF THE APOP COMMAND!
202        //  (apop is optional per rfc1939)
203
204        if(!isset($this->FP))
205            return $this->set_error( _("No connection to server"), __FUNCTION__ );
206           
207        if(!$this->ALLOWAPOP) {
208            $retVal = $this->login($login,$pass);
209            return $retVal;
210        } 
211       
212        if (empty($login))
213            return $this->set_error( _("No login ID submitted"), __FUNCTION__ );
214           
215        if(empty($pass))
216            return $this->set_error( _("No password submitted"), __FUNCTION__ );
217           
218       
219        $banner = $this->BANNER;
220        if( !$banner ) {
221            $this->set_error(  _("No server banner") . ' - ' . _("abort"), __FUNCTION__ );
222            $retVal = $this->login($login,$pass);
223            return $retVal;
224        }
225       
226        $AuthString = $banner. $pass;
227        $APOPString = md5($AuthString);
228       
229        $reply = $this->send_cmd("APOP $login $APOPString");
230        if(!$this->is_ok($reply)) {
231            $this->set_error( _("apop authentication failed") . ' - ' . _("abort"), __FUNCTION__ );
232            $retVal = $this->login($login,$pass);
233            return $retVal;
234        }
235       
236        //  Auth successful.
237        $this->COUNT = $this->last("count");
238        return $this->COUNT;
239    }
240
241    function login ($login = "", $pass = "") {
242        // Sends both user and pass. Returns # of msgs in mailbox or
243        // false on failure (or -1, if the error occurs while getting
244        // the number of messages.)
245
246        if( !isset($this->FP) ) {
247            return $this->set_error( _("No connection to server"), __FUNCTION__ );
248        } else {
249            $fp = $this->FP;
250            if( !$this->user( $login ) ) {
251                //  Preserve the error generated by user()
252                return false;
253            } else {
254                $count = $this->pass($pass);
255                if( (!$count) || ($count == -1) ) {
256                    //  Preserve the error generated by last() and pass()
257                    return false;
258                } else
259                    return $count;
260            }
261        }
262    }
263
264    function top ($msgNum, $numLines = 0) {
265        //  Gets the header and first $numLines of the msg body
266        //  returns data in an array with each returned line being
267        //  an array element. If $numLines is empty, returns
268        //  only the header information, and none of the body.
269
270        if(!isset($this->FP))
271            return $this->set_error( _("No connection to server"), __FUNCTION__ );
272       
273        $this->update_timer();
274
275        $this->send( "TOP $msgNum $numLines" );
276        $reply = $this->get_reply();
277        if (!$this->is_ok($reply)) {
278                return $this->set_error( _("Error ") . "[$reply]", __FUNCTION__ );
279        }
280
281        $MsgArray = array();
282        while ( ( $line = fgets($this->FP, $this->BUFFER) ) && !$this->iseof( $line ) ) {
283                $MsgArray[] = $line;
284        }
285
286        return $MsgArray;
287    }
288
289    function pop_list ($msgNum = '') {
290        //  If called with an argument, returns that msgs' size in octets
291        //  No argument returns an associative array of undeleted
292        //  msg numbers and their sizes in octets
293
294        if (!isset($this->FP))
295            return $this->set_error( _("No connection to server"), __FUNCTION__ );
296       
297        if ($this->COUNT === FALSE || $this->COUNT == -1) {
298                return false;
299        }
300       
301        if ( $this->COUNT == 0 ) {
302                return array(0,0);
303        }
304
305        $this->update_timer();
306               
307        if (!$msgNum) {
308                $this->send( "LIST $msgNum" );
309
310            $reply = $this->get_reply();
311            if (!$this->is_ok($reply))
312                return $this->set_error( _("Error ") . "[$reply]", __FUNCTION__ );
313               
314            // Gmail and some email providers return the word 'messages' in the response, example:
315            // +OK 307 messages (23191343 bytes)
316            preg_match_all('([0-9]+)', $reply, $m);
317            $size = FALSE;
318            if( !empty($m[0]) ) {
319                $size = array_pop($m[0]);
320            }
321            return $size;
322        }
323       
324        $reply = $this->send_cmd('LIST');
325        if (!$this->is_ok($reply))
326            return $this->set_error( _("Error ") .  "[$reply]", __FUNCTION__ );
327           
328        $msg = array();   
329        for( $i=1; $i <= $this->COUNT; $i++ ) {
330                $info = $this->get_reply();
331                if ( !empty($info) ) {
332                        $info = explode(' ', $info);
333                        $msg[ $info[0] ] = $info[1];
334                }
335        }
336       
337        return $msg;
338    }
339
340    function get ($msgNum) {
341        //  Retrieve the specified msg number. Returns an array
342        //  where each line of the msg is an array element.
343
344        if (!isset($this->FP))
345                return $this->set_error( _("No connection to server"), __FUNCTION__ );
346
347        $this->update_timer();
348        $reply = $this->send_cmd('RETR '. $msgNum);
349
350        if(!$this->is_ok($reply)) 
351                return $this->set_error( _("Error ") . "[$reply]", __FUNCTION__ );
352
353        $MsgArray = array();
354
355        while ( ( $line = fgets($this->FP, $this->BUFFER) ) && !$this->iseof( $line ) ) {
356                $MsgArray[] = $line;
357        }
358       
359        return $MsgArray;
360    }
361   
362    private function iseof( $line ) {
363        return $line == ".\r\n" || empty($line);
364    }
365
366    function last ( $type = "count" ) {
367        static $saved_last;
368        //  Returns the highest msg number in the mailbox.
369        //  returns -1 on error, 0+ on success, if type != count
370        //  results in a popstat() call (2 element array returned)
371
372        if (!isset($this->FP)) {
373                $this->set_error( _("No connection to server"), __FUNCTION__ );
374                return -1;
375        }
376
377        $reply = $this->send_cmd("STAT");
378        if(!$this->is_ok($reply)) {
379                $this->set_error( _("Error ") . "[$reply]", __FUNCTION__ );
380                return -1;
381        }
382
383        if( !$saved_last ) {
384                list( $ok, $count, $size) = explode( ' ', $reply);
385                $saved_last = array( $count, $size );
386        }
387       
388        return $type == 'count' ? $saved_last['count'] : $saved_last;
389    }
390
391    function reset () {
392        //  Resets the status of the remote server. This includes
393        //  resetting the status of ALL msgs to not be deleted.
394        //  This method automatically closes the connection to the server.
395
396        if (!isset($this->FP))
397                return $this->set_error( _("No connection to server"), __FUNCTION__ );
398               
399        $reply = $this->send_cmd("RSET");
400        if(!$this->is_ok($reply))
401        {
402            //  The POP3 RSET command -never- gives a -ERR
403            //  response - if it ever does, something truely
404            //  wild is going on.
405
406            $this->ERROR = "POP3 reset: " . _("Error ") . "[$reply]";
407            @error_log("POP3 reset: ERROR [$reply]",0);
408        }
409        $this->quit();
410        return true;
411    }
412
413    function send_cmd ( $cmd ) {
414        //  Sends a user defined command string to the
415        //  POP server and returns the results. Useful for
416        //  non-compliant or custom POP servers.
417
418        //  The return value is a standard fgets() call, which
419        //  will read up to $this->BUFFER bytes of data, until it
420        //  encounters a new line, or EOF, whichever happens first.
421
422        //  This method works best if $cmd responds with only
423        //  one line of data.
424
425        if (!isset($this->FP))
426            return $this->set_error( _("No connection to server"), __FUNCTION__ );
427
428        if (empty($cmd)) {
429            $this->set_error( _("Empty command string") , __FUNCTION__ );
430            return "";
431        }
432
433        $this->update_timer();
434        $reply = false;
435        if ( $this->send( $cmd ) ) 
436                $reply = $this->get_reply();
437       
438        return $reply;
439    }
440
441    function quit() {
442        //  Closes the connection to the POP3 server, deleting
443        //  any msgs marked as deleted.
444
445        if (!isset($this->FP))
446            return $this->set_error( _("Connection already closed."), __FUNCTION__ );
447       
448        $this->send('QUIT') && $this->get_reply();
449       
450        fclose($this->FP);
451        return true;
452    }
453
454    function popstat () {
455        //  Returns an array of 2 elements. The number of undeleted
456        //  msgs in the mailbox, and the size of the mbox in octets.
457
458        $PopArray = $this->last("array");
459
460        if ( empty($PopArray) || $PopArray == -1 )
461                return false;
462       
463        return $PopArray;
464    }
465
466    function uidl ($msgNum = "")
467    {
468        //  Returns the UIDL of the msg specified. If called with
469        //  no arguments, returns an associative array where each
470        //  undeleted msg num is a key, and the msg's uidl is the element
471        //  Array element 0 will contain the total number of msgs
472
473        if (!isset($this->FP)) 
474            return $this->set_error( _("No connection to server"), __FUNCTION__ );
475
476        if (!empty($msgNum)) {
477            $reply = $this->send_cmd("UIDL $msgNum");
478           
479            if(!$this->is_ok($reply))
480                return $this->set_error( _("Error ") . "[$reply]", __FUNCTION__ );
481           
482            list ($ok,$num,$myUidl) = preg_split('/\s+/',$reply);
483            return $myUidl;
484        }
485               
486        $this->update_timer();
487
488        $this->send('UIDL');
489        $reply = $this->get_reply();
490           
491        if ( !$this->is_ok($reply) ) 
492            return $this->set_error( _("Error ") . "[$reply]", __FUNCTION__ );
493
494                $response = array();
495        while ( ( $line = fgets($this->FP, $this->BUFFER) ) && !$this->iseof( $line ) ) {
496                $exploded = preg_split('#\s+#', $line);
497                $response[ $exploded[0] ] = $exploded[1];
498        }
499               
500        return $response;
501    }
502
503    function delete ($msgNum) {
504        //  Flags a specified msg as deleted. The msg will not
505        //  be deleted until a quit() method is called.
506
507        if (!isset($this->FP)) 
508                return $this->set_error( _("No connection to server"), __FUNCTION__ );
509               
510        if (empty($msgNum))
511            return $this->set_error( _("No msg number submitted"), __FUNCTION__ );
512
513        $reply = $this->send_cmd("DELE $msgNum");
514        if (!$this->is_ok($reply))
515                return $this->set_error( _("Command failed ") . "[$reply]", __FUNCTION__ );
516       
517        return true;
518    }
519
520    //  *********************************************************
521
522    //  The following methods are internal to the class.
523
524    function is_ok ($cmd) {
525        //  Return true or false on +OK or -ERR
526        return stristr($cmd, '+OK') !== FALSE;
527    }
528
529    function strip_clf ($text) {
530        // Strips \r\n from server responses
531
532        return str_replace(array("\r","\n"), '', $text);
533    }
534
535    function parse_banner ( $server_text ) {
536        if (empty($server_text)) {
537                return $server_text;
538        }
539       
540        preg_match_all('#<([^<>]+)>#', $server_text, $matches);
541       
542        $banners = array();
543        if (isset($matches[1]) && !empty($matches[1])) {
544                $banners = $matches[1];
545        }
546       
547        return implode('', $banners);
548    }
549   
550    function get_error() {
551        $error  =  $this->ERROR;
552        unset($this->ERROR);
553        return $error;
554    }
555
556}   // End class
557