<?php
/**
 * Selects plain text as the authentication method
 */
define('AUTH_PLAIN'0);

/**
 * Selects Kerberos V4 as the authentication method
 */
define('AUTH_KERBEROS_V4'1);

/**
 * Selects CRAM-MD5 as the authentication method
 */
define('AUTH_CRAM_MD5'2);

/**
 * Selects PAM as the authentication method
 */
define('AUTH_PAM'3);

/**
 * Selects TLS (imaps) as the authentication method
 */
define('AUTH_TLS'4);

/**
 * There was an error while attempting to connect to the specified server
 */
define('ERR_SERVER'1001);

/**
 * There was an error while attempting to login (Bad username or password?)
 */
define('ERR_BAD_LOGIN'1002);

/**
 * Unable to retreive the capabilities from the specified server
 */
define('ERR_NO_CAPS'1003);

/**
 * Unable to select the specified mailbox
 */
define('ERR_NO_SELECT'1004);

/**
 * Invalid UID (message id) specified or message not found
 */
define('ERR_INVALID_UID'1005);

/**
 * Specified message flag is not supported by the server
 */
define('ERR_FLAG_NOSUP'1006);

/**
 * No connection to the imap server
 */
define('ERR_NO_CONN'1007);

/**
 * Unable to store new flag mode on message
 */
define('ERR_STORE_FLAG'1008);

/**
 * Unable to expunge mailbox
 */
define('ERR_NO_EXPUNGE'1009);

/**
 * Auth method is not supported on server
 */
define('ERR_AUTH_UNSUPPORTED'1010);

/**
 * Lost lock on mailbox
 */
define('ERR_MBOX_LOCK'1011);

/**
 * Unable to close selected mailbox
 */
define('ERR_NO_CLOSE'1012);

/**
 * Unable to retreive list of known mailboxes
 */
define('ERR_NO_LIST'1013);

/**
 * Unimplemented
 */
define('ERR_NOT_IMPLEMENTED'1998);

/**
 * ...
 */
define('ERR_SNAFU'1999);

/**
 * Specifies that the number of total messages should be returned
 */
define('MAIL_TOTAL'2001);

/**
 * Specifies that the number of new messages should be returned
 */
define('MAIL_NEW'2002);

/**
 * Specifies that the number of recent (new) messages should be returned
 */
define('MAIL_RECENT'2003);

/**
 * Seen flag. Specifies that the message has flag \Seen
 */
define('FLAG_SEEN'1);

/**
 * Recent flag. Specifies that the message has flag \Recent
 */
define('FLAG_RECENT'2);

/**
 * Unseen flag. Specifies that the message has flag \Unseen
 */
define('FLAG_UNSEEN'4);

/**
 * Deleted flag. Specifies that the message has flag \Deleted
 */
define('FLAG_DELETED'8);

/**
 * Permanent flag. Specifies that the message has flag \*
 */
define('FLAG_PERM'16);

/**
 * Answered flag. Specifies that the message has flag \Answered
 */
define('FLAG_ANSWERED'32);

/**
 * Flagged flag. Specifies that the message has flag \Flagged
 */
define('FLAG_FLAGGED'64);

/**
 * Draft flag. Specifies that the message has flag \Draft
 */
define('FLAG_DRAFT'128);

/**
 * Specifies that the message has no flag set
 */
define('FLAG_NONE'256);

class 
CImap
{
    
/**
     * Connection ID for persistant IMAP connection
     * @access private
     */
    
var $_conid;
    
    
/**
     * Server address
     * @access private
     */
    
var $_server;
    
    
/**
     * Server port
     * @access private
     */
    
var $_port;
    
    
/**
     * Authentication method
     * @access private
     */
    
var $_auth_type;
    
    
/**
     * Currently selected mailbox
     * @access private
     */
    
var $_curmbox = array( /* currently selected mailbox (folder) */
        
'Name' => '',    /* Selected mailbox name */
        
'Exists' => 0,    /* Number of total messages */
        
'Recent' => 0,    /* Number of new (recent) messages */
        
'Unseen' => 0    /* Number of unseen messages (flagged \Unseen) */
    
);
    
    
/**
     * Error number
     * @access private
     */
    
var $_errno;
    
    
/**
     * Error string
     * @access private
     */
    
var $_errstr;
    
    
/**
     * Server capability string
     * @access private
     */
    
var $_caps/* server capabilities */
    
    /**
     * Supported message flags on the server
     * @access private
     */
    
var $_flags;

    
/**
     * Defines the flags that can be changed on a message
     * @access private
     */
    
var $_valid_flags = array(
        
'\\seen' => TRUE,
        
'\\recent' => FALSE,
        
'\\unseen' => FALSE,
        
'\\deleted' => TRUE,
        
'\\*' => FALSE,
        
'\\answered' => TRUE,
        
'\\flagged' => TRUE,
        
'\\draft' => TRUE
    
);
    
    
/**
     * 
     * @access private
     */
    
var $_flagmap = array(
        
'\\seen' => FLAG_SEEN,
        
'\\recent' => FLAG_RECENT,
        
'\\unseen' => FLAG_UNSEEN,
        
'\\deleted' => FLAG_DELETED,
        
'\\*' => FLAG_PERM,
        
'\\answered' => FLAG_ANSWERED,
        
'\\flagged' => FLAG_FLAGGED,
        
'\\draft' => FLAG_DRAFT
    
);
    
    
/**
     * Known mailboxes
     * @access private
     */
    
var $_known_boxes = array();
    
    
/**
     * Next prefix to send with commands
     * @access private
     */
    
var $_next_prefix/* holds the next prefix for the session sequence */
    
    /**
     * Old error reporting value
     * @access private
     */
    
var $_ye_olde_reporting;
    
    
/**
     * Constructor
     *
     * @param    string    Server to connect to
     * @param    int        Port to connect to
     * @param    int        Type of authentication to use (not implemented)
     * @param    bool    Debug mode
     */
    
function CImap($server$port 143$auth_type AUTH_PLAIN$debug FALSE)
    {
        
register_shutdown_function(array(&$this'_cleanup'));
        
$this->_ye_olde_reporting error_reporting();

        if(!
$debug)
            
$this->_ye_olde_reporting error_reporting(0);

        
$this->_conid NULL;
        
$this->_server $server;
        
$this->_port $port;
        
$this->_curmbox['Name'] = NULL;
        
$this->_curmbox['Exists'] = 0;
        
$this->_curmbox['Recent'] = 0;
        
$this->_curmbox['Unseen'] = 0;
        
$this->_errno 0;
        
$this->_errstr NULL;
        
$this->_caps NULL;
        
$this->_auth_type $auth_type;
        
$this->_enable_debug $debug;

        
$this->_next_prefix 'A0001';
    }
    
    
/**
     * Destructor
     * @access private
     */
    
function _cleanup()
    {
        
$this->_conid NULL;
        
$this->_server NULL;
        
$this->_port NULL;
        
$this->_curmbox['Name'] = NULL;
        
$this->_curmbox['Exists'] = 0;
        
$this->_curmbox['Recent'] = 0;
        
$this->_curmbox['Unseen'] = 0;
        
$this->_errno 0;
        
$this->_errstr NULL;
        
$this->_caps NULL;
        
$this->_enable_debug FALSE;
        
        
$this->_next_prefix 'A0001';
        
        
error_reporting($this->_ye_olde_reporting);
    }
    
    
/**
     * Returns the number of messages in mailbox
     *
     * @param    int        Type of messages to count
     * @return    int        Number of messages
     */
    
function nummsgs($mbox NULL$stat MAIL_TOTAL)
    {
        
$oldmbox $this->_curmbox['Name'];
        
        if((
$mbox == NULL) || (empty($mbox)))
            
$mbox $this->_curmbox['Name'];
        
        
$this->select($mbox);

        switch(
$stat)
        {
            case 
MAIL_TOTAL: return($this->_curmbox['Exists']);
            case 
MAIL_NEW: return($this->_curmbox['Recent']);
            case 
MAIL_UNSEEN: return($this->_curmbox['Unseen']);
        }
        
        if(
$oldmbox != NULL && !empty($oldmbox))
            
$this->select($oldmbox);
        
        return(
0);
    }
    
    
/**
     * Gracefully prints out a debug message
     *
     * @param    mixed    Message to print out
     */
    
function _debug($str)
    {
        if(
$this->_enable_debug)
        {
            
$oldr error_reporting(E_ALL);
            
trigger_error($strE_USER_NOTICE);
            
error_reporting($oldr);
        }
    }
    
    
/**
     * Establishes a connection to the specified server
     *
     * @param    bool    Specifies whether or not to retreive server capabilities (default false)
     * @return    bool    TRUE upon success, FALSE upon error. Sets _errno and _errstr
     *                    If $caps is set to TRUE the return value of capabilities() will be
     *                    returned.
     * @see capabilities
     */
    
function connect($caps FALSE)
    {
        
$buf '';
        
$this->_conid fsockopen($this->_server$this->_port$this->_errno$this->_errstr20);
        
        if(!
is_resource($this->_conid)) { $this->_errno ERR_SERVER; return(FALSE); }
        
        
$buf fgets($this->_conid);
        if(!
strstr($buf'OK'))
        {
            
/* cannot connect to server */
            
fclose($this->_conid);
            
            
$this->_errno ERR_SERVER;
            
$this->_conid NULL;
            
            return(
FALSE);
        }
        
        
set_time_limit(60);
        
        if(
$caps)
            return(
$this->capabilities());
        
        return(
TRUE);
    }
    
    
/**
     * Terminates an established connection from the IMAP server
     *
     * @return    string    Data sent from the server upon disconnection
     */
    
function disconnect()
    {
        if(!
is_resource($this->_conid)) { return(''); }
        
        
$this->_next_prefix++;
        
fputs($this->_conid$this->_next_prefix.' LOGOUT'."\n");
        
$buf fgets($this->_conid);
        
fclose($this->_conid);
        
        return(
$buf);
    }
    
    
/**
     * Attempts to authenticate user with the imap server using supplied credentials
     *
     * @param    string    Username to authenticate
     * @param    string    Password to use for authentication
     * @return    bool    TRUE upon success, FALSE upon error. Sets _errno and _errstr
     */
    
function login($username$password)
    {
        if(!
is_resource($this->_conid)) { $this->_errno ERR_NO_CONN; return(FALSE); }        
        
$this->_next_prefix++;
        
        
/**
         * Simple hack for now until other methods are supported
         */
        
if($this->_auth_type != AUTH_PLAIN)
        {
            
$this->_errno ERR_AUTH_UNSUPPORTED;
            
$this->_errstr 'Authentication method selected is not supported on the server.';
            
            return(
FALSE);
        }
        
        
fputs($this->_conid$this->_next_prefix.' LOGIN '.$username.' '.$password."\n");
        
        
$buf fgets($this->_conid);        
        if(
strncmp($this->_next_prefix.' OK'$bufstrlen($this->_next_prefix) + 3))
        {
            
/* not logged in -- rejected */
            
$this->_errno ERR_BAD_LOGIN;
            
$this->_errstr $buf;

            return(
FALSE);
        }

        
$this->_known_boxes $this->get_mailboxes();
        return(
TRUE);
    }
    
    
/**
     * Retreives server capabilities
     *
     * @return    bool    TRUE upon success, FALSE upon error. Sets _errno and _errstr
     */
    
function capabilities()
    {
        if(!
is_resource($this->_conid)) { $this->_errno ERR_NO_CONN; return(FALSE); }
        
        
$this->_next_prefix++;
        
fputs($this->_conid$this->_next_prefix.' CAPABILITY'."\n");
        
$this->_caps fgets($this->_conid);
        
        
$buf fgets($this->_conid);
        if(
strncmp($this->_next_prefix.' OK'$bufstrlen($this->_next_prefix) + 3))
        {
            
/* cannot retreive capabilities from server */
            
$this->_errno ERR_NO_CAPS;
            
$this->_errstr $buf;

            return(
FALSE);
        }
        
        return(
TRUE);
    }
    
    
/**
     * Attempts to select supplied mailbox as current one
     *
     * @param    string    Mailbox to switch to
     * @return    bool    TRUE upon success, FALSE upon error. Sets _errno and _errstr
     */
    
function select($mbox)
    {
        if(!
is_resource($this->_conid)) { $this->_errno ERR_NO_CONN; return(FALSE); }        
        
$this->_next_prefix++;
        
        if((!
$this->valid_mailbox($mbox)) || ($this->translate_mbox_name($mbox) == NULL))
        {
            
$this->_errno ERR_NO_SELECT;
            
$this->_errstr 'Invalid Mailbox.';
            return(
FALSE);
        }
        
        
$this->_curmbox['Name'] = $this->translate_mbox_name($mbox);
        
fputs($this->_conid$this->_next_prefix.' SELECT '.($this->_curmbox['Name'])."\n");
        
$buf fgets($this->_conid); /* should be something like * 172 EXISTS */
        
if(!strncmp($this->_next_prefix.' NO'$bufstrlen($this->_next_prefix) + 3))
        {
            
/* cannot select mailbox folder */
            
$this->_errno ERR_NO_SELECT;
            
$this->_errstr $buf;

            return(
FALSE);
        }
        
        
$t $buf;
        
$buf = array();
        
$buf[] = $t;
        
        while(
strncmp($this->_next_prefix.' OK'$buf[count($buf) - 1], 7))
            
$buf[] = fgets($this->_conid);
        
        for(
$i 0$i count($buf); $i++)
        {
            
$this->_debug($buf[$i]);
            if(
strstr($buf[$i], 'EXISTS'))
            {
                
$tmp explode(' '$buf[$i]);
                
$this->_curmbox['Exists'] = $tmp[1];
            }
            else if(
strstr($buf[$i], 'RECENT'))
            {
                
$tmp explode(' '$buf[$i]);
                
$this->_curmbox['Recent'] = $tmp[1];
            }
            else if(
strstr($buf[$i], 'UNSEEN'))
            {
                
$tmp explode(' '$buf[$i]);
                
$this->_curmbox['Unseen'] = str_replace(']'''$tmp[3]);
            }
            else if(
strstr($buf[$i], 'FLAGS'))
            {
                
$start strpos($buf[$i], '(');
                
$end strpos($buf[$i], ')');
                
$this->_flags substr(trim($buf[$i]), $start 1, ($end $start) - 1);
            }
        }

        return(
TRUE);
    }
    
    
/**
     * Closes the currently open mailbox
     *
     * @return    bool    TRUE upon success, FALSE upon failure
     */
    
function unselect()
    {
        if(!
is_resource($this->_conid)) { $this->_errno ERR_NO_CONN; return(FALSE); }
        
$this->_next_prefix++;
        
        
fputs($this->_conid$this->_next_prefix.' SELECT '.($this->_curmbox['Name'])."\n");
        
        
$buf fgets($this->_conid);        
        if(!
strncmp($this->_next_prefix.' OK'$bufstrlen($this->_next_prefix) + 3))
        {
            
$this->_curmbox['Name'] = NULL;
            
$this->_curmbox['Exists'] = 0;
            
$this->_curmbox['Recent'] = 0;
            
$this->_curmbox['Unseen'] = 0;
            return(
TRUE);
        }
        
        
/* cannot close mailbox folder */
        
$this->_errno ERR_NO_CLOSE;
        
$this->_errstr $buf;

        return(
FALSE);
    }
    
    
/**
     * Returns the headers for the supplied messages
     *
     * Note that this does NOT use the UID of the message(s). Doing so will
     * mess up your output and maybe even your perception of reality ;)
     *
     * @param    int        Starting number of messages to retrieve (default 1)
     * @param    int        Number of messages to retrieve (use -1 to retreive all)
     * @param    int        Bitmask of flags to pass as a filter parameter
     * @return    array    Sorted array of retreived headers
     */
    
function headers($start 1$count 0$optarg FLAG_NONE)
    {
        if(!
is_resource($this->_conid)) { $this->_errno ERR_NO_CONN; return(NULL); }
        
        
$this->_next_prefix++;
        if(
$start <= 0$start 1;
        if(
$count <= || $count $this->_curmbox['Exists']) $count $this->_curmbox['Exists'];
        
        
/**
         * TODO
         * If $optarg != FLAG_NONE then we need to SEARCH for messages
         * matching the $optarg parameters. This will greatly aid in
         * searching for messages with a specific flag.
         */
        /* A654 FETCH 1:20 (FLAGS UID RFC822.SIZE RFC822.HEADER) */
        
fputs($this->_conid$this->_next_prefix.' FETCH '.$start.':'.$count.' (FLAGS UID INTERNALDATE RFC822.SIZE RFC822.HEADER)'."\n");

        
$line '';
        
$headers '';
        while(
strncmp($this->_next_prefix.' OK'$linestrlen($this->_next_prefix) + 3))
        {
            if(!
strncmp($this->_next_prefix.' BAD'$linestrlen($this->_next_prefix) + 4))
            {
                
/* bad message id or something */
                
$this->_errno ERR_INVALID_UID;
                
$this->_errstr $line;

                return(
NULL);
            }
            else
                
$headers .= $line;

            
$line fgets($this->_conid);
        }
        
$ret = array();
        
        
$tmp explode("\n"$headers);

        
$j 0;
        for(
$i 0$i count($tmp); $i++)
        {
            
$istart strpos($tmp[$i], '* ') + 2;
            
$istop strpos($tmp[$i], ' FETCH');
            
$ret[$j]['id'] = substr($tmp[$i], $istart, ($istop $istart));
            
            
$istart strpos($tmp[$i], 'FLAGS ') + 7;
            
$istop strpos($tmp[$i], ') UID');
            
$ret[$j]['flags'] = substr($tmp[$i], $istart, ($istop $istart));
            
            
$istart strpos($tmp[$i], 'INTERNALDATE ') + 13;
            
$istop strpos($tmp[$i], ' RFC822.SIZE');
            
$ret[$j]['date'] = strtotime(str_replace(array('\'''"'), ''substr($tmp[$i], $istart, ($istop $istart))));
            
            
$istart strpos($tmp[$i], 'UID ') + 4;
            
$istop strpos($tmp[$i], ' INTERNALDATE');
            
$ret[$j]['uid'] = substr($tmp[$i], $istart, ($istop $istart));
            
            
$istart strpos($tmp[$i], 'RFC822.SIZE ') + 12;
            
$istop strpos($tmp[$i], ' RFC822.HEADER');
            
$ret[$j]['size'] = substr($tmp[$i], $istart, ($istop $istart));
            
            
$istart strpos($tmp[$i], 'RFC822.HEADER ') + 14;
            
$istop strlen($tmp[$i]);
            
$ret[$j]['headers']['size'] = str_replace(array('('')''{''}'), ''trim(substr($tmp[$i], $istart, ($istop $istart))));

            
$i++;
            
$ret[$j]['headers']['raw'] = '';
            while((
$i count($tmp)) && (trim($tmp[$i]) != ')'))
            {
                
$ret[$j]['headers']['raw'] .= $tmp[$i];
                
$i++;
            }
            
            
$ret[$j]['headers']['raw'] = trim($ret[$j]['headers']['raw']);
            
            
/**
             * Now iterate through the headers and add each entry
             * to a new array
             */
            
$ret[$j]['headers']['raw'] = str_replace(array("\r""\r\n"), "\n"$ret[$j]['headers']['raw']);
            
$stub explode("\n"$ret[$j]['headers']['raw']);
            
$h_new = array();
            
$l 0;
            for(
$k 0$k count($stub); $k++)
            {
                if(
strpos($stub[$k], ': '))
                {
                    if(!
strncmp(strtolower($stub[$k]), 'from:'5))
                    {
                        
$td trim(str_replace(substr($stub[$k], 05), ''$stub[$k]));
                        
$tt explode('<'$td);
                        if(empty(
$tt[0])  || strlen(trim($tt[0])) == 0)
                            
$ret[$j]['from']['name'] = '';
                        else
                            
$ret[$j]['from']['name'] = str_replace('"'''trim($tt[0]));

                        if(empty(
$tt[1])  || strlen(trim($tt[1])) == 0)
                            
$ret[$j]['from']['email'] = '';
                        else
                            
$ret[$j]['from']['email'] = str_replace('>'''trim($tt[1]));
                    }
                    else if(!
strncmp(strtolower($stub[$k]), 'reply-to:'9))
                    {
                        
$td trim(str_replace(substr($stub[$k], 09), ''$stub[$k]));
                        
$tt explode('<'$td);
                        
                        if(empty(
$tt[0])  || strlen(trim($tt[0])) == 0)
                            
$ret[$j]['reply-to']['name'] = '';
                        else
                            
$ret[$j]['reply-to']['name'] = str_replace('"'''trim($tt[0]));

                        if(empty(
$tt[1])  || strlen(trim($tt[1])) == 0)
                            
$ret[$j]['reply-to']['email'] = '';
                        else
                            
$ret[$j]['reply-to']['email'] = str_replace('>'''trim($tt[1]));
                    }
                    else if(!
strncmp(strtolower($stub[$k]), 'subject:'8))
                    {
                        
$td trim(str_replace(substr($stub[$k], 08), ''$stub[$k]));
                        
$ret[$j]['subject'] = $td;
                    }
                    else if(!
strncmp(strtolower($stub[$k]), 'envelope-to:'12))
                    {
                        
$td trim(str_replace(substr($stub[$k], 012), ''$stub[$k]));
                        
$tt explode("<"$td);
                        if(empty(
$tt[0]) || strlen(trim($tt[0])) == 0)
                            
$ret[$j]['envelope-to']['name'] = '';
                        else
                            
$ret[$j]['envelope-to']['name'] = str_replace('"'''trim($tt[0]));

                        if(empty(
$tt[1]) || strlen(trim($tt[1])) == 0)
                            
$ret[$j]['envelope-to']['email'] = str_replace('>'''$td);