OSDN Git Service

fa71070cec0858e6f1d66e753e7f0b712ff7b387
[nucleus-jp/nucleus-plugins.git] / trunk / NP_Moblog / sharedlibs / Net / POP3.php
1 <?php
2 // +-----------------------------------------------------------------------+
3 // | Copyright (c) 2002, Richard Heyes                                     |
4 // | All rights reserved.                                                  |
5 // |                                                                       |
6 // | Redistribution and use in source and binary forms, with or without    |
7 // | modification, are permitted provided that the following conditions    |
8 // | are met:                                                              |
9 // |                                                                       |
10 // | o Redistributions of source code must retain the above copyright      |
11 // |   notice, this list of conditions and the following disclaimer.       |
12 // | o Redistributions in binary form must reproduce the above copyright   |
13 // |   notice, this list of conditions and the following disclaimer in the |
14 // |   documentation and/or other materials provided with the distribution.|
15 // | o The names of the authors may not be used to endorse or promote      |
16 // |   products derived from this software without specific prior written  |
17 // |   permission.                                                         |
18 // |                                                                       |
19 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
20 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
21 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
23 // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
25 // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
28 // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
30 // |                                                                       |
31 // +-----------------------------------------------------------------------+
32 // | Author: Richard Heyes <richard@phpguru.org>                           |
33 // | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar>               |
34 // +-----------------------------------------------------------------------+
35 //
36 // $Id: POP3.php,v 1.1 2008-05-04 07:04:50 hsur Exp $
37
38 require_once('Net/Socket.php');
39
40
41
42 /**
43 *  +----------------------------- IMPORTANT ------------------------------+
44 *  | Usage of this class compared to native php extensions such as IMAP   |
45 *  | is slow and may be feature deficient. If available you are STRONGLY  |
46 *  | recommended to use the php extensions.                               |
47 *  +----------------------------------------------------------------------+
48 *
49 * POP3 Access Class
50 *
51 * For usage see the example script
52 */
53
54 define('NET_POP3_STATE_DISCONNECTED',  1, true);
55 define('NET_POP3_STATE_AUTHORISATION', 2, true);
56 define('NET_POP3_STATE_TRANSACTION',   4, true);
57
58 class Net_POP3 {
59
60     /*
61     * Some basic information about the mail drop
62     * garnered from the STAT command
63     *
64     * @var array
65     */
66     var $_maildrop;
67
68     /*
69     * Used for APOP to store the timestamp
70     *
71     * @var string
72     */
73     var $_timestamp;
74
75     /*
76     * Timeout that is passed to the socket object
77     *
78     * @var integer
79     */
80     var $_timeout;
81
82     /*
83     * Socket object
84     *
85     * @var object
86     */
87     var $_socket;
88
89     /*
90     * Current state of the connection. Used with the
91     * constants defined above.
92     *
93     * @var integer
94     */
95     var $_state;
96
97     /*
98     * Hostname to connect to
99     *
100     * @var string
101     */
102     var $_host;
103
104     /*
105     * Port to connect to
106     *
107     * @var integer
108     */
109     var $_port;
110
111     /**
112     * To allow class debuging
113     * @var boolean
114     */
115     var $_debug = false;
116
117
118     /**
119     * The auth methods this class support
120     * @var array
121     */
122     //var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
123     //Disabling DIGEST-MD5 for now
124     var $supportedAuthMethods=array( 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
125     //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
126     //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');
127
128
129     /**
130     * The auth methods this class support
131     * @var array
132     */
133     var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5');
134
135
136     /**
137     * The capability response
138     * @var array
139     */
140     var $_capability;
141
142    /*
143     * Constructor. Sets up the object variables, and instantiates
144     * the socket object.
145     *
146     */
147
148
149     function Net_POP3()
150     {
151         $this->_timestamp =  ''; // Used for APOP
152         $this->_maildrop  =  array();
153         $this->_timeout   =  3;
154         $this->_state     =  NET_POP3_STATE_DISCONNECTED;
155         $this->_socket    =& new Net_Socket();
156         /*
157         * Include the Auth_SASL package.  If the package is not available,
158         * we disable the authentication methods that depend upon it.
159         */
160         if ((@include_once 'Auth/SASL.php') == false) {
161             if($this->_debug){
162                 echo "AUTH_SASL NOT PRESENT!\n";
163             }
164             foreach($this->supportedSASLAuthMethods as $SASLMethod){
165                 $pos = array_search( $SASLMethod, $this->supportedAuthMethods );
166                 if($this->_debug){
167                     echo "DISABLING METHOD $SASLMethod\n";
168                 }
169                 unset($this->supportedAuthMethods[$pos]);
170             }
171         }
172
173
174
175     }
176
177
178     /**
179     * Handles the errors the class can find
180     * on the server
181     *
182     * @access private
183     * @return PEAR_Error
184     */
185
186     function _raiseError($msg, $code =-1)
187     {
188     include_once 'PEAR.php';
189     return PEAR::raiseError($msg, $code);
190     }
191
192
193     
194     /*
195     * Connects to the given host on the given port.
196     * Also looks for the timestamp in the greeting
197     * needed for APOP authentication
198     *
199     * @param  string $host Hostname/IP address to connect to
200     * @param  string $port Port to use to connect to on host
201     * @return bool  Success/Failure
202     */
203     function connect($host = 'localhost', $port = 110)
204     {
205         $this->_host = $host;
206         $this->_port = $port;
207
208         $result = $this->_socket->connect($host, $port, false, $this->_timeout);
209         if ($result === true) {
210             $data = $this->_recvLn();
211
212             if( $this->_checkResponse($data) ){
213             // if the response begins with '+OK' ...
214 //            if (@substr(strtoupper($data), 0, 3) == '+OK') {
215                 // Check for string matching apop timestamp
216                 if (preg_match('/<.+@.+>/U', $data, $matches)) {
217                     $this->_timestamp = $matches[0];
218                 }
219                 $this->_maildrop = array();
220                 $this->_state    = NET_POP3_STATE_AUTHORISATION;
221
222                 return true;
223             }
224         }
225
226         $this->_socket->disconnect();
227         return false;
228     }
229
230     /*
231     * Disconnect function. Sends the QUIT command
232     * and closes the socket.
233     *
234     * @return bool Success/Failure
235     */
236     function disconnect()
237     {
238         return $this->_cmdQuit();
239     }
240
241     /*
242     * Performs the login procedure. If there is a timestamp
243     * stored, APOP will be tried first, then basic USER/PASS.
244     *
245     * @param  string $user Username to use
246     * @param  string $pass Password to use
247     * @param  mixed $apop Whether to try APOP first, if used as string you can select the auth methd to use ( $pop3->login('validlogin', 'validpass', "CRAM-MD5");
248     *          Valid methods are: 'DIGEST-MD5','CRAM-MD5','LOGIN','PLAIN','APOP','USER' 
249     * @return mixed  true on Success/ PEAR_ERROR on error
250     */
251     function login($user, $pass, $apop = true)
252     {
253         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
254
255             if(PEAR::isError($ret= $this->_cmdAuthenticate($user , $pass , $apop ) ) ){
256                 return $ret;
257             }
258             if( ! PEAR::isError($ret)){
259                 $this->_state = NET_POP3_STATE_TRANSACTION;
260                 return true;
261             }
262
263         }
264         return $this->_raiseError('Generic login error' , 1);
265     }
266
267
268
269     /**
270     * Parses the response from the capability command. Stores
271     * the result in $this->_capability
272     *
273     * @access private
274     */
275     function _parseCapability()
276     {
277
278         if(!PEAR::isError($data = $this->_sendCmd('CAPA'))){
279             $data = $this->_getMultiline();
280         }else {
281             // CAPA command not supported, reset data var
282             //  to avoid Notice errors of preg_split on an object
283             $data = '';
284         }
285         $data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY);
286
287         for ($i = 0; $i < count($data); $i++) {
288
289             $capa='';
290             if (preg_match('/^([a-z,\-]+)( ((.*))|$)$/i', $data[$i], $matches)) {
291
292                 $capa=strtolower($matches[1]);
293                 switch ($capa) {
294                     case 'implementation':
295                         $this->_capability['implementation'] = $matches[3];
296                         break;
297                     case 'sasl':
298                         $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
299                         break;
300                     default :
301                         $this->_capability[$capa] = $matches[2];
302                         break;
303                 }
304             }
305         }
306     }
307
308
309
310
311     /**
312      * Returns the name of the best authentication method that the server
313      * has advertised.
314      *
315      * @param string if !=null,authenticate with this method ($userMethod).
316      *
317      * @return mixed    Returns a string containing the name of the best
318      *                  supported authentication method or a PEAR_Error object
319      *                  if a failure condition is encountered.
320      * @access private
321      * @since  1.0
322      */
323     function _getBestAuthMethod($userMethod = null)
324     {
325
326 /*
327        return 'USER';
328        return 'APOP';
329        return 'DIGEST-MD5';
330        return 'CRAM-MD5';
331 */
332
333
334         $this->_parseCapability();
335
336         //unset($this->_capability['sasl']);
337
338        if( isset($this->_capability['sasl']) ){
339            $serverMethods=$this->_capability['sasl'];
340        }else{
341             $serverMethods=array('USER');
342             // Check for timestamp before attempting APOP
343             if ($this->_timestamp != null)
344             {
345                 $serverMethods[] = 'APOP';
346             }
347        }
348
349         if($userMethod !== null && $userMethod !== true ){
350             $methods = array();
351             $methods[] = $userMethod;
352             return $userMethod;
353         }else{
354             $methods = $this->supportedAuthMethods;
355         }
356
357         if( ($methods != null) && ($serverMethods != null)){
358
359             foreach ( $methods as $method ) {
360
361                 if ( in_array( $method , $serverMethods ) ) {
362                     return $method;
363                 }
364             }
365             $serverMethods=implode(',' , $serverMethods );
366             $myMethods=implode(',' ,$this->supportedAuthMethods);
367             return $this->_raiseError("$method NOT supported authentication method!. This server " .
368                 "supports these methods: $serverMethods, but I support $myMethods");
369         }else{
370             return $this->_raiseError("This server don't support any Auth methods");
371         }
372     }
373
374
375
376
377
378
379     /* Handles the authentication using any known method
380      *
381      * @param string The userid to authenticate as.
382      * @param string The password to authenticate with.
383      * @param string The method to use ( if $usermethod == '' then the class chooses the best method (the stronger is the best ) )
384      *
385      * @return mixed  string or PEAR_Error
386      *
387      * @access private
388      * @since  1.0
389      */
390     function _cmdAuthenticate($uid , $pwd , $userMethod = null )
391     {
392
393
394         if ( PEAR::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
395             return $method;
396         }
397
398         switch ($method) {
399             case 'DIGEST-MD5':
400                 $result = $this->_authDigest_MD5( $uid , $pwd );
401                 break;
402             case 'CRAM-MD5':
403                 $result = $this->_authCRAM_MD5( $uid , $pwd );
404                 break;
405             case 'LOGIN':
406                 $result = $this->_authLOGIN( $uid , $pwd );
407                 break;
408             case 'PLAIN':
409                 $result = $this->_authPLAIN( $uid , $pwd );
410                 break;
411             case 'APOP':
412                 $result = $this->_cmdApop( $uid , $pwd );
413                 // if APOP fails fallback to USER auth
414                 if( PEAR::isError( $result ) ){
415                     //echo "APOP FAILED!!!\n";
416                     $result=$this->_authUSER( $uid , $pwd );
417                 }
418                 break;
419             case 'USER':
420                 $result = $this->_authUSER( $uid , $pwd );
421             break;
422
423
424             default :
425                 $result = $this->_raiseError( "$method is not a supported authentication method" );
426                 break;
427         }
428         return $result;
429     }
430
431
432
433
434      /* Authenticates the user using the USER-PASS method.
435      *
436      * @param string The userid to authenticate as.
437      * @param string The password to authenticate with.
438      *
439      * @return mixed    true on success or PEAR_Error on failure
440      *
441      * @access private
442      * @since  1.0
443      */
444     function _authUSER($user, $pass  )
445     {
446         if ( PEAR::isError($ret=$this->_cmdUser($user) ) ){
447             return $ret;
448         }
449         if ( PEAR::isError($ret=$this->_cmdPass($pass) ) ){
450             return $ret;
451         }
452         return true;
453     }
454
455
456
457
458
459
460
461
462      /* Authenticates the user using the PLAIN method.
463      *
464      * @param string The userid to authenticate as.
465      * @param string The password to authenticate with.
466      *
467      * @return array Returns an array containing the response
468      *
469      * @access private
470      * @since  1.0
471      */
472     function _authPLAIN($user, $pass  )
473     {
474         $cmd=sprintf('AUTH PLAIN %s', base64_encode( chr(0) . $user . chr(0) . $pass ) );
475
476         if ( PEAR::isError( $ret = $this->_send($cmd) ) ) {
477             return $ret;
478         }
479         if ( PEAR::isError( $challenge = $this->_recvLn() ) ){
480             return $challenge;
481         }
482         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
483             return $ret;
484         }
485         
486         return true;
487     }
488
489
490
491      /* Authenticates the user using the PLAIN method.
492      *
493      * @param string The userid to authenticate as.
494      * @param string The password to authenticate with.
495      *
496      * @return array Returns an array containing the response
497      *
498      * @access private
499      * @since  1.0
500      */
501     function _authLOGIN($user, $pass  )
502     {
503         $this->_send('AUTH LOGIN');
504
505         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
506             return $challenge;
507         }
508         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
509             return $ret;
510         }
511
512
513         if ( PEAR::isError( $ret = $this->_send(sprintf('%s', base64_encode($user))) ) ) {
514             return $ret;
515         }
516
517         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
518             return $challenge;
519         }
520         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
521             return $ret;
522         }
523
524         if ( PEAR::isError( $ret = $this->_send(sprintf('%s', base64_encode($pass))) ) ) {
525             return $ret;
526         }
527
528         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
529             return $challenge;
530         }
531         return $this->_checkResponse($challenge);
532     }
533
534
535
536
537
538      /* Authenticates the user using the CRAM-MD5 method.
539      *
540      * @param string The userid to authenticate as.
541      * @param string The password to authenticate with.
542      *
543      * @return array Returns an array containing the response
544      *
545      * @access private
546      * @since  1.0
547      */
548     function _authCRAM_MD5($uid, $pwd )
549     {
550         if ( PEAR::isError( $ret = $this->_send( 'AUTH CRAM-MD5' ) ) ) {
551             return $ret;
552         }
553
554         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
555             return $challenge;
556         }
557         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
558             return $ret;
559         }
560
561         // remove '+ '
562         
563         $challenge=substr($challenge,2);
564         
565         $challenge = base64_decode( $challenge );
566
567         $cram = &Auth_SASL::factory('crammd5');
568         $auth_str = base64_encode( $cram->getResponse( $uid , $pwd , $challenge ) );
569
570
571         if ( PEAR::isError($error = $this->_send( $auth_str ) ) ) {
572             return $error;
573         }
574         if ( PEAR::isError( $ret = $this->_recvLn() ) ) {
575             return $ret;
576         }
577         //echo "RET:$ret\n";
578         return $this->_checkResponse($ret);
579     }
580
581
582
583      /* Authenticates the user using the DIGEST-MD5 method.
584      *
585      * @param string The userid to authenticate as.
586      * @param string The password to authenticate with.
587      * @param string The efective user
588      *
589      * @return array Returns an array containing the response
590      *
591      * @access private
592      * @since  1.0
593      */
594     function _authDigest_MD5($uid, $pwd)
595     {
596         if ( PEAR::isError( $ret = $this->_send( 'AUTH DIGEST-MD5' ) ) ) {
597             return $ret;
598         }
599
600         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
601             return $challenge;
602         }
603         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
604             return $ret;
605         }
606
607         // remove '+ '
608         $challenge=substr($challenge,2);
609
610         $challenge = base64_decode( $challenge );
611         $digest = &Auth_SASL::factory('digestmd5');
612         $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge, "localhost", "pop3" ));
613
614         if ( PEAR::isError($error = $this->_send( $auth_str ) ) ) {
615             return $error;
616         }
617
618         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
619             return $challenge;
620         }
621         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
622             return $ret;
623         }
624          /*
625          * We don't use the protocol's third step because POP3 doesn't allow
626          * subsequent authentication, so we just silently ignore it.
627          */
628
629         if ( PEAR::isError( $challenge = $this->_send("\r\n") ) ) {
630             return $challenge ;
631         }
632         
633         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
634             return $challenge;
635         }
636         
637         return $this->_checkResponse($challenge);
638         
639
640     }
641
642
643
644
645
646
647
648
649
650
651     /*
652     * Sends the APOP command
653     *
654     * @param  $user Username to send
655     * @param  $pass Password to send
656     * @return bool Success/Failure
657     */
658     function _cmdApop($user, $pass)
659     {
660         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
661
662             if (!empty($this->_timestamp)) {
663                 if(PEAR::isError($data = $this->_sendCmd('APOP ' . $user . ' ' . md5($this->_timestamp . $pass)) ) ){
664                     return $data;
665                 }
666                 $this->_state = NET_POP3_STATE_TRANSACTION;
667                 return true;
668             }
669         }
670         return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State1');
671     }
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687     /*
688     * Returns the raw headers of the specified message.
689     *
690     * @param  integer $msg_id Message number
691     * @return mixed   Either raw headers or false on error
692     */
693     function getRawHeaders($msg_id)
694     {
695         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
696             return $this->_cmdTop($msg_id, 0);
697         }
698
699         return false;
700     }
701
702     /*
703     * Returns the  headers of the specified message in an
704     * associative array. Array keys are the header names, array
705     * values are the header values. In the case of multiple headers
706     * having the same names, eg Received:, the array value will be
707     * an indexed array of all the header values.
708     *
709     * @param  integer $msg_id Message number
710     * @return mixed   Either array of headers or false on error
711     */
712     function getParsedHeaders($msg_id)
713     {
714         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
715
716             $raw_headers = rtrim($this->getRawHeaders($msg_id));
717
718             $raw_headers = preg_replace("/\r\n[ \t]+/", ' ', $raw_headers); // Unfold headers
719             $raw_headers = explode("\r\n", $raw_headers);
720             foreach ($raw_headers as $value) {
721                 $name  = substr($value, 0, $pos = strpos($value, ':'));
722                 $value = ltrim(substr($value, $pos + 1));
723                 if (isset($headers[$name]) AND is_array($headers[$name])) {
724                     $headers[$name][] = $value;
725                 } elseif (isset($headers[$name])) {
726                     $headers[$name] = array($headers[$name], $value);
727                 } else {
728                     $headers[$name] = $value;
729                 }
730             }
731
732             return $headers;
733         }
734
735         return false;
736     }
737
738     /*
739     * Returns the body of the message with given message number.
740     *
741     * @param  integer $msg_id Message number
742     * @return mixed   Either message body or false on error
743     */
744     function getBody($msg_id)
745     {
746         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
747             $msg = $this->_cmdRetr($msg_id);
748             return substr($msg, strpos($msg, "\r\n\r\n")+4);
749         }
750
751         return false;
752     }
753
754     /*
755     * Returns the entire message with given message number.
756     *
757     * @param  integer $msg_id Message number
758     * @return mixed   Either entire message or false on error
759     */
760     function getMsg($msg_id)
761     {
762         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
763             return $this->_cmdRetr($msg_id);
764         }
765
766         return false;
767     }
768
769     /*
770     * Returns the size of the maildrop
771     *
772     * @return mixed Either size of maildrop or false on error
773     */
774     function getSize()
775     {
776         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
777             if (isset($this->_maildrop['size'])) {
778                 return $this->_maildrop['size'];
779             } else {
780                 list(, $size) = $this->_cmdStat();
781                 return $size;
782             }
783         }
784
785         return false;
786     }
787
788     /*
789     * Returns number of messages in this maildrop
790     *
791     * @return mixed Either number of messages or false on error
792     */
793     function numMsg()
794     {
795         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
796             if (isset($this->_maildrop['num_msg'])) {
797                 return $this->_maildrop['num_msg'];
798             } else {
799                 list($num_msg, ) = $this->_cmdStat();
800                 return $num_msg;
801             }
802         }
803
804         return false;
805     }
806
807     /*
808     * Marks a message for deletion. Only will be deleted if the
809     * disconnect() method is called.
810     *
811     * @param  integer $msg_id Message to delete
812     * @return bool Success/Failure
813     */
814     function deleteMsg($msg_id)
815     {
816         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
817             return $this->_cmdDele($msg_id);
818         }
819
820         return false;
821     }
822
823     /*
824     * Combination of LIST/UIDL commands, returns an array
825     * of data
826     *
827     * @param  integer $msg_id Optional message number
828     * @return mixed Array of data or false on error
829     */
830     function getListing($msg_id = null)
831     {
832     
833         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
834             if (!isset($msg_id)){
835             
836                 $list=array();
837                 if ($list = $this->_cmdList()) {
838                     if ($uidl = $this->_cmdUidl()) {
839                         foreach ($uidl as $i => $value) {
840                             $list[$i]['uidl'] = $value['uidl'];
841                         }
842                     }
843                     return $list;
844                 }else{
845                     return array();
846                 }
847             } else {
848                 if ($list = $this->_cmdList($msg_id) AND $uidl = $this->_cmdUidl($msg_id)) {
849                     return array_merge($list, $uidl);
850                 }
851             }
852         }
853
854         return false;
855     }
856
857     /*
858     * Sends the USER command
859     *
860     * @param  string $user Username to send
861     * @return bool  Success/Failure
862     */
863     function _cmdUser($user)
864     {
865         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
866             return $this->_sendCmd('USER ' . $user);
867         }
868         return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
869     }
870
871
872     /*
873     * Sends the PASS command
874     *
875     * @param  string $pass Password to send
876     * @return bool  Success/Failure
877     */
878     function _cmdPass($pass)
879     {
880         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
881             return $this->_sendCmd('PASS ' . $pass);
882         }
883         return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
884     }
885
886
887     /*
888     * Sends the STAT command
889     *
890     * @return mixed Indexed array of number of messages and
891     *               maildrop size, or false on error.
892     */
893     function _cmdStat()
894     {
895         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
896             if(!PEAR::isError($data = $this->_sendCmd('STAT'))){
897                 sscanf($data, '+OK %d %d', $msg_num, $size);
898                 $this->_maildrop['num_msg'] = $msg_num;
899                 $this->_maildrop['size']    = $size;
900
901                 return array($msg_num, $size);
902             }
903         }
904         return false;
905     }
906
907
908     /*
909     * Sends the LIST command
910     *
911     * @param  integer $msg_id Optional message number
912     * @return mixed   Indexed array of msg_id/msg size or
913     *                 false on error
914     */
915     function _cmdList($msg_id = null)
916     {
917         $return=array();
918         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
919             if (!isset($msg_id)) {
920                 if(!PEAR::isError($data = $this->_sendCmd('LIST') )){
921                     $data = $this->_getMultiline();
922                     $data = explode("\r\n", $data);                    
923                     foreach ($data as $line) {
924                         if($line !=''){
925                             sscanf($line, '%s %s', $msg_id, $size);
926                             $return[] = array('msg_id' => $msg_id, 'size' => $size);
927                         }
928                     }
929                     return $return;
930                 }
931             } else {
932                 if(!PEAR::isError($data = $this->_sendCmd('LIST ' . $msg_id))){
933                     if($data!=''){
934                         sscanf($data, '+OK %d %d', $msg_id, $size);
935                         return array('msg_id' => $msg_id, 'size' => $size);
936                     }
937                     return array();
938                 }
939             }
940         }
941         
942
943         return false;
944     }
945
946
947     /*
948     * Sends the RETR command
949     *
950     * @param  integer $msg_id The message number to retrieve
951     * @return mixed   The message or false on error
952     */
953     function _cmdRetr($msg_id)
954     {
955         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
956             if(!PEAR::isError($data = $this->_sendCmd('RETR ' . $msg_id) )){
957                 $data = $this->_getMultiline();
958                 return $data;
959             }
960         }
961
962         return false;
963     }
964
965
966     /*
967     * Sends the DELE command
968     *
969     * @param  integer $msg_id Message number to mark as deleted
970     * @return bool Success/Failure
971     */
972     function _cmdDele($msg_id)
973     {
974         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
975             return $this->_sendCmd('DELE ' . $msg_id);
976         }
977
978         return false;
979     }
980
981
982     /*
983     * Sends the NOOP command
984     *
985     * @return bool Success/Failure
986     */
987     function _cmdNoop()
988     {
989         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
990             if(!PEAR::isError($data = $this->_sendCmd('NOOP'))){
991                 return true;
992             }
993         }
994
995         return false;
996     }
997
998     /*
999     * Sends the RSET command
1000     *
1001     * @return bool Success/Failure
1002     */
1003     function _cmdRset()
1004     {
1005         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
1006             if(!PEAR::isError($data = $this->_sendCmd('RSET'))){
1007                 return true;
1008             }
1009         }
1010
1011         return false;
1012     }
1013
1014     /*
1015     * Sends the QUIT command
1016     *
1017     * @return bool Success/Failure
1018     */
1019     function _cmdQuit()
1020     {
1021         $data = $this->_sendCmd('QUIT');
1022         $this->_state = NET_POP3_STATE_DISCONNECTED;
1023         $this->_socket->disconnect();
1024
1025         return (bool)$data;
1026     }
1027
1028
1029     /*
1030     * Sends the TOP command
1031     *
1032     * @param  integer  $msg_id    Message number
1033     * @param  integer  $num_lines Number of lines to retrieve
1034     * @return mixed Message data or false on error
1035     */
1036     function _cmdTop($msg_id, $num_lines)
1037     {
1038         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
1039
1040             if(!PEAR::isError($data = $this->_sendCmd('TOP ' . $msg_id . ' ' . $num_lines))){
1041                 return $this->_getMultiline();
1042             }
1043         }
1044
1045         return false;
1046     }
1047
1048     /*
1049     * Sends the UIDL command
1050     *
1051     * @param  integer $msg_id Message number
1052     * @return mixed indexed array of msg_id/uidl or false on error
1053     */
1054     function _cmdUidl($msg_id = null)
1055     {
1056         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
1057
1058             if (!isset($msg_id)) {
1059                 if(!PEAR::isError($data = $this->_sendCmd('UIDL') )){
1060                     $data = $this->_getMultiline();
1061                     $data = explode("\r\n", $data);
1062                     foreach ($data as $line) {
1063                         sscanf($line, '%d %s', $msg_id, $uidl);
1064                         $return[] = array('msg_id' => $msg_id, 'uidl' => $uidl);
1065                     }
1066
1067                     return $return;
1068                 }
1069             } else {
1070
1071                 $data = $this->_sendCmd('UIDL ' . $msg_id);
1072                 sscanf($data, '+OK %d %s', $msg_id, $uidl);
1073                 return array('msg_id' => $msg_id, 'uidl' => $uidl);
1074             }
1075         }
1076
1077         return false;
1078     }
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088     /*
1089     * Sends a command, checks the reponse, and
1090     * if good returns the reponse, other wise
1091     * returns false.
1092     *
1093     * @param  string $cmd  Command to send (\r\n will be appended)
1094     * @return mixed First line of response if successful, otherwise false
1095     */
1096     function _sendCmd($cmd)
1097     {
1098         if (PEAR::isError($result = $this->_send($cmd) )){
1099             return $result ;
1100         }
1101
1102         if (PEAR::isError($data = $this->_recvLn() )){
1103             return $data;
1104         }
1105         
1106         if ( strtoupper(substr($data, 0, 3)) == '+OK') {
1107             return $data;
1108         }
1109         
1110         
1111         return $this->_raiseError($data);
1112     }
1113
1114     /*
1115     * Reads a multiline reponse and returns the data
1116     *
1117     * @return string The reponse.
1118     */
1119     function _getMultiline()
1120     {
1121         $data = '';
1122         while(!PEAR::isError($tmp = $this->_recvLn() ) ) {
1123             if($tmp == '.'){
1124                 return substr($data, 0, -2);
1125             }
1126             if (substr($tmp, 0, 2) == '..') {
1127                 $tmp = substr($tmp, 1);
1128             }
1129             $data .= $tmp . "\r\n";
1130         }
1131         return substr($data, 0, -2);
1132     }
1133
1134
1135    /**
1136     * Sets the bebug state
1137     *
1138     * @param  bool $debug 
1139     * @access public
1140     * @return void
1141     */
1142     function setDebug($debug=true)
1143     {
1144         $this->_debug=$debug;
1145     }
1146
1147
1148
1149
1150
1151    /**
1152      * Send the given string of data to the server.
1153      *
1154      * @param   string  $data       The string of data to send.
1155      *
1156      * @return  mixed   True on success or a PEAR_Error object on failure.
1157      *
1158      * @access  private
1159      * @since   1.0
1160      */
1161     function _send($data)
1162     {
1163         if ($this->_debug) {
1164             echo "C: $data\n";
1165         }
1166
1167         if (PEAR::isError($error = $this->_socket->writeLine($data))) {
1168             return $this->_raiseError('Failed to write to socket: ' . $error->getMessage());
1169         }
1170         return true;
1171     }
1172
1173
1174
1175      /**
1176      * Receive the given string of data from the server.
1177      *
1178      * @return  mixed   a line of response on success or a PEAR_Error object on failure.
1179      *
1180      * @access  private
1181      * @since  1.0
1182      */
1183     function _recvLn()
1184     {
1185         if (PEAR::isError( $lastline = $this->_socket->readLine( 8192 ) ) ) {
1186             return $this->_raiseError('Failed to write to socket: ' . $this->lastline->getMessage() );
1187         }
1188         if($this->_debug){
1189             // S: means this data was sent by  the POP3 Server
1190             echo "S:$lastline\n" ;
1191         }
1192         return $lastline;
1193     }
1194
1195      /**
1196      * Checks de server Response
1197      *
1198      * @param  string $response the response
1199      * @return  mixed   true on success or a PEAR_Error object on failure.
1200      *
1201      * @access  private
1202      * @since  1.3.3
1203      */
1204
1205     function _checkResponse($response)
1206     {
1207         if (@substr(strtoupper($response), 0, 3) == '+OK') {
1208             return true;
1209         }else{
1210             if (@substr(strtoupper($response), 0, 4) == '-ERR') {
1211                 return $this->_raiseError($response);
1212             }else{
1213                 if (@substr(strtoupper($response), 0, 2) == '+ ') {
1214                     return true;
1215                 }
1216             }
1217     
1218         }
1219         return $this->_raiseError("Unknown Response ($response)");
1220     }
1221     
1222
1223
1224 }
1225
1226 ?>