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