Source for file POP3.php
Documentation is available at POP3.php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002, Richard Heyes |
// | All rights reserved. |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@phpguru.org> |
// | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar> |
// +-----------------------------------------------------------------------+
// $Id: POP3.php 295243 2010-02-18 22:05:20Z clockwerx $
require_once 'Net/Socket.php';
* +----------------------------- IMPORTANT ------------------------------+
* | Usage of this class compared to native php extensions such as IMAP |
* | is slow and may be feature deficient. If available you are STRONGLY |
* | recommended to use the php extensions. |
* +----------------------------------------------------------------------+
* For usage see the example script
define('NET_POP3_STATE_DISCONNECTED', 1 , true );
define('NET_POP3_STATE_AUTHORISATION', 2 , true );
define('NET_POP3_STATE_TRANSACTION', 4 , true );
* Some basic information about the mail drop
* garnered from the STAT command
* Used for APOP to store the timestamp
* Timeout that is passed to the socket object
* Current state of the connection. Used with the
* constants defined above.
* To allow class debuging
* The auth methods this class support
//var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
//Disabling DIGEST-MD5 for now
var $supportedAuthMethods=array ( 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
//var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
//var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');
* The auth methods this class support
var $supportedSASLAuthMethods=array ('DIGEST-MD5', 'CRAM-MD5');
* The capability response
* Constructor. Sets up the object variables, and instantiates
$this->_timestamp = ''; // Used for APOP
$this->_maildrop = array ();
$this->_socket = new Net_Socket ();
* Include the Auth_SASL package. If the package is not available,
* we disable the authentication methods that depend upon it.
@include_once 'Auth/SASL.php';
echo "AUTH_SASL NOT PRESENT!\n";
foreach ($this->supportedSASLAuthMethods as $SASLMethod) {
$pos = array_search($SASLMethod, $this->supportedAuthMethods);
echo " DISABLING METHOD $SASLMethod\n";
unset ($this->supportedAuthMethods[$pos]);
* Handles the errors the class can find
function _raiseError ($msg, $code =-1 )
return PEAR ::raiseError ($msg, $code);
* Connects to the given host on the given port.
* Also looks for the timestamp in the greeting
* needed for APOP authentication
* @param string $host Hostname/IP address to connect to
* @param string $port Port to use to connect to on host
* @return bool Success/Failure
function connect ($host = 'localhost', $port = 110 )
$result = $this->_socket->connect ($host, $port, false , $this->_timeout);
$data = $this->_recvLn ();
if( $this->_checkResponse ($data) ){
// if the response begins with '+OK' ...
// if (@substr(strtoupper($data), 0, 3) == '+OK') {
// Check for string matching apop timestamp
$this->_timestamp = $matches[0 ];
$this->_maildrop = array ();
$this->_socket->disconnect ();
* Disconnect function. Sends the QUIT command
* @return bool Success/Failure
return $this->_cmdQuit ();
* Performs the login procedure. If there is a timestamp
* stored, APOP will be tried first, then basic USER/PASS.
* @param string $user Username to use
* @param string $pass Password to use
* @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");
* Valid methods are: 'DIGEST-MD5','CRAM-MD5','LOGIN','PLAIN','APOP','USER'
* @return mixed true on Success/ PEAR_ERROR on error
function login ($user, $pass, $apop = true )
if (PEAR ::isError ($ret= $this->_cmdAuthenticate ($user, $pass, $apop))){
if (!PEAR ::isError ($ret)) {
return $this->_raiseError ('Generic login error' , 1 );
* Parses the response from the capability command. Stores
* the result in $this->_capability
function _parseCapability ()
if(!PEAR ::isError ($data = $this->_sendCmd ('CAPA'))){
$data = $this->_getMultiline ();
// CAPA command not supported, reset data var
// to avoid Notice errors of preg_split on an object
$data = preg_split('/\r?\n/', $data, -1 , PREG_SPLIT_NO_EMPTY );
for ($i = 0; $i < count($data); $i++ ) {
if (preg_match('/^([a-z,\-]+)( ((.*))|$)$/i', $data[$i], $matches)) {
$this->_capability['implementation'] = $matches[3 ];
$this->_capability['sasl'] = preg_split('/\s+/', $matches[3 ]);
$this->_capability[$capa] = $matches[2 ];
* Returns the name of the best authentication method that the server
* @param string if !=null,authenticate with this method ($userMethod).
* @return mixed Returns a string containing the name of the best
* supported authentication method or a PEAR_Error object
* if a failure condition is encountered.
function _getBestAuthMethod ($userMethod = null )
$this->_parseCapability ();
//unset($this->_capability['sasl']);
if (isset ($this->_capability['sasl']) ){
$serverMethods= $this->_capability['sasl'];
$serverMethods=array ('USER');
// Check for timestamp before attempting APOP
if ($this->_timestamp != null )
$serverMethods[] = 'APOP';
if ($userMethod !== null && $userMethod !== true ) {
$methods[] = $userMethod;
$methods = $this->supportedAuthMethods;
if (($methods != null ) && ($serverMethods != null )) {
foreach ($methods as $method ) {
if (in_array($method, $serverMethods)) {
$serverMethods= implode(',' , $serverMethods );
$myMethods= implode(',' ,$this->supportedAuthMethods);
return $this->_raiseError (" $method NOT supported authentication method!. This server " .
" supports these methods: $serverMethods, but I support $myMethods" );
return $this->_raiseError ("This server don't support any Auth methods");
* Handles the authentication using any known method
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @param string The method to use ( if $usermethod == '' then the class chooses the best method (the stronger is the best ) )
* @return mixed string or PEAR_Error
function _cmdAuthenticate ($uid , $pwd , $userMethod = null )
if (PEAR ::isError ($method = $this->_getBestAuthMethod ($userMethod))) {
if (!in_array($userMethod, $this->supportedAuthMethods)) {
return $this->_raiseError (
'Authentication method "' . $userMethod . '" is not supported.'
$result = $this->_authDigest_MD5 ( $uid , $pwd );
$result = $this->_authCRAM_MD5 ( $uid , $pwd );
$result = $this->_authLOGIN ( $uid , $pwd );
$result = $this->_authPLAIN ( $uid , $pwd );
$result = $this->_cmdApop ( $uid , $pwd );
// if APOP fails fallback to USER auth
if (PEAR ::isError ($result)){
//echo "APOP FAILED!!!\n";
$result= $this->_authUSER ( $uid , $pwd );
$result = $this->_authUSER ( $uid , $pwd );
$result = $this->_raiseError ( " $method is not a supported authentication method" );
* Authenticates the user using the USER-PASS method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return mixed true on success or PEAR_Error on failure
function _authUSER ($user, $pass )
if ( PEAR ::isError ($ret= $this->_cmdUser ($user) ) ){
if ( PEAR ::isError ($ret= $this->_cmdPass ($pass) ) ){
* Authenticates the user using the PLAIN method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return array Returns an array containing the response
function _authPLAIN ($user, $pass )
if ( PEAR ::isError ( $ret = $this->_send ($cmd) ) ) {
if ( PEAR ::isError ( $challenge = $this->_recvLn () ) ){
if( PEAR ::isError ($ret= $this->_checkResponse ($challenge) )){
* Authenticates the user using the PLAIN method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return array Returns an array containing the response
function _authLOGIN ($user, $pass )
$this->_send ('AUTH LOGIN');
if ( PEAR ::isError ( $challenge = $this->_recvLn () ) ) {
if( PEAR ::isError ($ret= $this->_checkResponse ($challenge) )){
if ( PEAR ::isError ( $challenge = $this->_recvLn () ) ) {
if( PEAR ::isError ($ret= $this->_checkResponse ($challenge) )){
if ( PEAR ::isError ( $challenge = $this->_recvLn () ) ) {
return $this->_checkResponse ($challenge);
* Authenticates the user using the CRAM-MD5 method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @return array Returns an array containing the response
function _authCRAM_MD5 ($uid, $pwd )
if (PEAR ::isError ($ret = $this->_send ('AUTH CRAM-MD5'))) {
if (PEAR ::isError ($challenge = $this->_recvLn ())) {
if (PEAR ::isError ($ret= $this->_checkResponse ($challenge))) {
$challenge= substr($challenge,2 );
$cram = &Auth_SASL ::factory ('crammd5');
$auth_str = base64_encode($cram->getResponse ($uid , $pwd , $challenge));
if (PEAR ::isError ($error = $this->_send ($auth_str))) {
if (PEAR ::isError ($ret = $this->_recvLn ())) {
return $this->_checkResponse ($ret);
* Authenticates the user using the DIGEST-MD5 method.
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
* @param string The efective user
* @return array Returns an array containing the response
function _authDigest_MD5 ($uid, $pwd)
if ( PEAR ::isError ( $ret = $this->_send ( 'AUTH DIGEST-MD5' ) ) ) {
if ( PEAR ::isError ( $challenge = $this->_recvLn () ) ) {
if( PEAR ::isError ($ret= $this->_checkResponse ($challenge) )){
$challenge= substr($challenge,2 );
$digest = &Auth_SASL ::factory ('digestmd5');
$auth_str = base64_encode($digest->getResponse ($uid, $pwd, $challenge, "localhost", "pop3" ));
if ( PEAR ::isError ($error = $this->_send ( $auth_str ) ) ) {
if ( PEAR ::isError ( $challenge = $this->_recvLn () ) ) {
if( PEAR ::isError ($ret= $this->_checkResponse ($challenge) )){
* We don't use the protocol's third step because POP3 doesn't allow
* subsequent authentication, so we just silently ignore it.
if ( PEAR ::isError ( $challenge = $this->_send ("\r\n") ) ) {
if ( PEAR ::isError ( $challenge = $this->_recvLn () ) ) {
return $this->_checkResponse ($challenge);
* @param $user Username to send
* @param $pass Password to send
* @return bool Success/Failure
function _cmdApop ($user, $pass)
if (!empty ($this->_timestamp)) {
if(PEAR ::isError ($data = $this->_sendCmd ('APOP ' . $user . ' ' . md5($this->_timestamp . $pass)) ) ){
return $this->_raiseError ('Not In NET_POP3_STATE_AUTHORISATION State1');
* Returns the raw headers of the specified message.
* @param integer $msg_id Message number
* @return mixed Either raw headers or false on error
function getRawHeaders ($msg_id)
return $this->_cmdTop ($msg_id, 0 );
* Returns the headers of the specified message in an
* associative array. Array keys are the header names, array
* values are the header values. In the case of multiple headers
* having the same names, eg Received:, the array value will be
* an indexed array of all the header values.
* @param integer $msg_id Message number
* @return mixed Either array of headers or false on error
function getParsedHeaders ($msg_id)
$raw_headers = rtrim($this->getRawHeaders ($msg_id));
$raw_headers = preg_replace("/\r\n[ \t]+/", ' ', $raw_headers); // Unfold headers
$raw_headers = explode("\r\n", $raw_headers);
foreach ($raw_headers as $value) {
if (isset ($headers[$name]) AND is_array($headers[$name])) {
$headers[$name][] = $value;
} elseif (isset ($headers[$name])) {
$headers[$name] = array ($headers[$name], $value);
$headers[$name] = $value;
* Returns the body of the message with given message number.
* @param integer $msg_id Message number
* @return mixed Either message body or false on error
function getBody ($msg_id)
$msg = $this->_cmdRetr ($msg_id);
* Returns the entire message with given message number.
* @param integer $msg_id Message number
* @return mixed Either entire message or false on error
return $this->_cmdRetr ($msg_id);
* Returns the size of the maildrop
* @return mixed Either size of maildrop or false on error
if (isset ($this->_maildrop['size'])) {
return $this->_maildrop['size'];
list (, $size) = $this->_cmdStat ();
* Returns number of messages in this maildrop
* @return mixed Either number of messages or false on error
if (isset ($this->_maildrop['num_msg'])) {
return $this->_maildrop['num_msg'];
list ($num_msg, ) = $this->_cmdStat ();
* Marks a message for deletion. Only will be deleted if the
* disconnect() method is called.
* @param integer $msg_id Message to delete
* @return bool Success/Failure
function deleteMsg ($msg_id)
return $this->_cmdDele ($msg_id);
* Combination of LIST/UIDL commands, returns an array
* @param integer $msg_id Optional message number
* @return mixed Array of data or false on error
function getListing ($msg_id = null )
if ($list = $this->_cmdList ()) {
if ($uidl = $this->_cmdUidl ()) {
foreach ($uidl as $i => $value) {
$list[$i]['uidl'] = $value['uidl'];
if ($list = $this->_cmdList ($msg_id) AND $uidl = $this->_cmdUidl ($msg_id)) {
* @param string $user Username to send
* @return bool Success/Failure
return $this->_sendCmd ('USER ' . $user);
return $this->_raiseError ('Not In NET_POP3_STATE_AUTHORISATION State');
* @param string $pass Password to send
* @return bool Success/Failure
return $this->_sendCmd ('PASS ' . $pass);
return $this->_raiseError ('Not In NET_POP3_STATE_AUTHORISATION State');
* @return mixed Indexed array of number of messages and
* maildrop size, or false on error.
if(!PEAR ::isError ($data = $this->_sendCmd ('STAT'))){
sscanf($data, '+OK %d %d', $msg_num, $size);
$this->_maildrop['num_msg'] = $msg_num;
$this->_maildrop['size'] = $size;
return array ($msg_num, $size);
* @param integer $msg_id Optional message number
* @return mixed Indexed array of msg_id/msg size or
function _cmdList ($msg_id = null )
if(!PEAR ::isError ($data = $this->_sendCmd ('LIST') )){
$data = $this->_getMultiline ();
foreach ($data as $line) {
sscanf($line, '%s %s', $msg_id, $size);
$return[] = array ('msg_id' => $msg_id, 'size' => $size);
if(!PEAR ::isError ($data = $this->_sendCmd ('LIST ' . $msg_id))){
sscanf($data, '+OK %d %d', $msg_id, $size);
return array ('msg_id' => $msg_id, 'size' => $size);
* @param integer $msg_id The message number to retrieve
* @return mixed The message or false on error
function _cmdRetr ($msg_id)
if(!PEAR ::isError ($data = $this->_sendCmd ('RETR ' . $msg_id) )){
$data = $this->_getMultiline ();
* @param integer $msg_id Message number to mark as deleted
* @return bool Success/Failure
function _cmdDele ($msg_id)
return $this->_sendCmd ('DELE ' . $msg_id);
* @return bool Success/Failure
if(!PEAR ::isError ($data = $this->_sendCmd ('NOOP'))){
* @return bool Success/Failure
if(!PEAR ::isError ($data = $this->_sendCmd ('RSET'))){
* @return bool Success/Failure
$data = $this->_sendCmd ('QUIT');
$this->_socket->disconnect ();
* @param integer $msg_id Message number
* @param integer $num_lines Number of lines to retrieve
* @return mixed Message data or false on error
function _cmdTop ($msg_id, $num_lines)
if(!PEAR ::isError ($data = $this->_sendCmd ('TOP ' . $msg_id . ' ' . $num_lines))){
return $this->_getMultiline ();
* @param integer $msg_id Message number
* @return mixed indexed array of msg_id/uidl or false on error
function _cmdUidl ($msg_id = null )
if(!PEAR ::isError ($data = $this->_sendCmd ('UIDL') )){
$data = $this->_getMultiline ();
foreach ($data as $line) {
sscanf($line, '%d %s', $msg_id, $uidl);
$return[] = array ('msg_id' => $msg_id, 'uidl' => $uidl);
$data = $this->_sendCmd ('UIDL ' . $msg_id);
sscanf($data, '+OK %d %s', $msg_id, $uidl);
return array ('msg_id' => $msg_id, 'uidl' => $uidl);
* Sends a command, checks the reponse, and
* if good returns the reponse, other wise
* @param string $cmd Command to send (\r\n will be appended)
* @return mixed First line of response if successful, otherwise false
if (PEAR ::isError ($result = $this->_send ($cmd) )){
if (PEAR ::isError ($data = $this->_recvLn () )){
return $this->_raiseError ($data);
* Reads a multiline reponse and returns the data
* @return string The reponse.
while (!PEAR ::isError ($tmp = $this->_recvLn () ) ) {
if($tmp == '.' || $tmp == "\n."){
if (substr($tmp, 0 , 2 ) == '..') {
function setDebug ($debug=true )
* Send the given string of data to the server.
* @param string $data The string of data to send.
* @return mixed True on success or a PEAR_Error object on failure.
if (PEAR ::isError ($error = $this->_socket->writeLine ($data))) {
return $this->_raiseError ('Failed to write to socket: ' . $error->getMessage ());
* Receive the given string of data from the server.
* @return mixed a line of response on success or a PEAR_Error object on failure.
if (PEAR ::isError ($lastline = $this->_socket->readLine (8192 ))) {
return $this->_raiseError ('Failed to read from socket: ' . $this->lastline->getMessage ());
// S: means this data was sent by the POP3 Server
* Checks de server Response
* @param string $response the response
* @return mixed true on success or a PEAR_Error object on failure.
function _checkResponse ($response)
return $this->_raiseError ($response);
return $this->_raiseError (" Unknown Response ($response)" );
Documentation generated on Mon, 11 Mar 2019 15:41:21 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|