<?php

/* List of available protocols. */
$protocols = array(
    'POP3 Auto'                 =>  _("POP3 Auto"),
    'POP3'                      =>  _("POP3"),
    'POP3/notls'                =>  _("POP3, no TLS"),
    'POP3/ssl/novalidate-cert'  =>  _("POP3 over SSL (self-signed)"),
    'IMAP Auto'                 =>  _("IMAP Auto"),
    'IMAP'                      =>  _("IMAP"),
    'IMAP/notls'                =>  _("IMAP, no TLS"),
    'IMAP/ssl/novalidate-cert'  =>  _("IMAP over SSL (self-signed)")
);

/* List of Auto POP3 protocols. */
$pop3_protocols = array('POP3/ssl', 'POP3/ssl/novalidate-cert', 'POP3/notls', 'POP3');

/* List of Auto IMAP protocols. */
$imap_protocols = array('IMAP/ssl', 'IMAP/ssl/novalidate-cert', 'IMAP/notls', 'IMAP');

/*
 * Various classes used for fetchmail functionality.
 *
 * $Horde: imp/lib/Fetchmail.php,v 1.31 2003/07/03 18:49:04 slusarz Exp $
 *
 * Copyright 2002-2003 Nuno Loureiro <nuno@co.sapo.pt>
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 */

/**
 * The Account:: class provides an interface to all mail accounts a user
 * might have.
 *
 * @author  Nuno Loureiro <nuno@co.sapo.pt>
 * @version $Revision: 1.31 $
 * @since   IMP 4.0
 * @package imp
 */
class Account {

    /**
     * Array containing all the user's accounts.
     *
     * @var array $_accounts
     */
    var $_accounts = array();


    /**
     * Constructor.
     * Reads all the user's accounts from the prefs object or builds
     * a new account from the standard values given in prefs.php.
     *
     * @access public
     */
    function Account()
    {
        global $prefs;

        $accounts = @unserialize($prefs->getValue('accounts'));
        if (is_array($accounts)) {
            $this->_accounts = $accounts;
        }
    }

    /**
     * Saves all accounts in the prefs backend.
     *
     * @access public
     */
    function save()
    {
        global $prefs;

        $prefs->setValue('accounts', serialize($this->_accounts));
    }

    /**
     * Adds a new empty account to the array of accounts.
     *
     * @access public
     *
     * @return integer  The pointer to the created account.
     */
    function add()
    {
        $this->_accounts[] = array();
        return count($this->_accounts) - 1;
    }

    /**
     * Remove an account from the array of accounts
     *
     * @access public
     *
     * @param integer $account  The pointer to the account to be removed.
     *
     * @return array  The removed account
     */
    function delete($account)
    {
        $deleted = array_splice($this->_accounts, $account, 1);
        $this->save();
        return $deleted;
    }

    /**
     * Returns a property from one of the accounts.
     *
     * @access public
     *
     * @param string $key                The property to retrieve.
     * @param optional integer $account  The account to retrieve the property
     *                                   from.
     *
     * @return mixed  The value of the property.
     */
    function getValue($key, $account = 0)
    {
        $val = '';

        if (!array_key_exists($account, $this->_accounts)) {
            $account = 0;
        }

        if (array_key_exists($key, $this->_accounts[$account])) {
            $val = $this->_accounts[$account][$key];
        }

        return $val;
    }

    /**
     * Returns an array with the specified property from all existing
     * accounts.
     *
     * @access public
     *
     * @param string $key  The property to retrieve.
     *
     * @return array  The array with the values from all accounts.
     */
    function getAll($key)
    {
        $list = array();
        foreach (array_keys($this->_accounts) as $account) {
            $list[$account] = $this->getValue($key, $account);
        }

        return $list;
    }

    /**
     * Sets a property with a specified value.
     *
     * @access public
     *
     * @param string $key                The property to set.
     * @param mixed $val                 The value to which the property
     *                                   should be set.
     * @param optional integer $account  The account to set the property in.
     */
    function setValue($key, $val, $account = 0)
    {
        $this->_accounts[$account][$key] = $val;
    }

    /**
     * Returns true if the pair key/value is already in the accounts array.
     *
     * @access public
     *
     * @param string $key          The account key to search.
     * @param string $valueA       The value to search for in $key.
     *
     * @return boolean  True if the $value was found in $key.
     */
    function hasValue($key, $valueA)
    {
        $list = $this->getAll($key);
        foreach ($list as $valueB) {
            if (strpos(String::lower($valueA), String::lower($valueB)) !== false) {
                return true;
            }
        }
        return false;
    }

}


/**
 * The FetchSrv:: class provides an interface to download mail from remote
 * mail servers.
 *
 * @author  Nuno Loureiro <nuno@co.sapo.pt>
 * @version $Revision: 1.31 $
 * @since   IMP 4.0
 * @package imp
 */
class FetchSrv {

    /**
     * Store parameters for this driver here
     *
     * @var array $_params
     */
    var $_params;

    /**
     * Number of Messages Fetched
     *
     * @var integer $numMsgs
     */
    var $_numMsgs = 0;


    /**
     * Constructor.
     *
     * @access public
     *
     * @param optional array $params  TODO.
     */
    function FetchSrv($params = array())
    {
        $list = array('rmailbox' => 'INBOX', 'lmailbox' => 'INBOX',
                      'username' => '', 'password' => '', 'server' => '',
                      'protocol' => '', 'acctcolor' => '');
        foreach ($list as $key => $val) {
            if (array_key_exists($key, $params) &&
                !empty($params[$key])) {
                $this->_params[$key] = $params[$key];
            } else {
                $this->_params[$key] = $val;
            }
        }

        $list = array('del', 'onlynew', 'markseen', 'loginfetch');
        foreach ($list as $val) {
            if (array_key_exists($val, $params) &&
                (($params[$val] == 'yes') ||
                 ($params[$val] == '1') ||
                 ($params[$val] === true))) {
                $this->_params[$val] = true;
            } else {
                $this->_params[$val] = false;
            }
        }

        $this->_getBaseProtocol();
    }

    /**
     * Determine the base protocol of the protocol.
     *
     * @access private
     */
    function _getBaseProtocol()
    {
        $protocols = explode('/', $this->_params['protocol']);
        if (is_array($protocols)) {
            $this->_params['base_protocol'] = $protocols[0];
        } else {
            $this->_params['base_protocol'] = $this->_params['protocol'];
        }
    }

    /**
     * Do all types of sanity checks.
     *
     * @access private
     *
     * @param optional string $protocol  A protocol string to use.
     *
     * @return string  Returns a string with the error or 'ok' if success.
     */
    function _sanity()
    {
        if (empty($this->_params['username'])) {
            return PEAR::raiseError(_("You didn't specify the username"));
        }

        if (empty($this->_params['password'])) {
            return PEAR::raiseError(_("You didn't specify the password"));
        }

        if (empty($this->_params['server'])) {
            return PEAR::raiseError(_("You didn't specify a Mail Server"));
        }

        $this->_getBaseProtocol();

        switch ($this->_params['protocol']) {

        case 'IMAP':
        case 'IMAP/notls':
            $this->_params['port'] = 143;
            break;

        case 'IMAP/ssl':
        case 'IMAP/ssl/novalidate-cert':
            $this->_params['port'] = 993;
            break;

        case 'POP3':
        case 'POP3/notls':
            $this->_params['port'] = 110;
            break;

        case 'POP3/ssl':
        case 'POP3/ssl/novalidate-cert':
            $this->_params['port'] = 995;
            break;

        default:
            return PEAR::raiseError(_("Unknown Protocol Specified"));
            break;

        }

        return true;
    }

    /**
     * Checks if the remote mailbox exists.
     *
     * @access private
     *
     * @return boolean  Does the remote mailbox exist?
     */
    function _remoteMboxExists($stream)
    {
        $connstr = '{' . $this->_params['server'] . ':' . $this->_params['port'] . '/'. String::lower($this->_params['protocol']) . '}';

        if (empty($this->_params['rmailbox'])) {
            return false;
        }

        if ($this->_params['rmailbox'] == 'INBOX') {
            /* Inbox always exists and is a special case */
            return true;
        }

        if (@imap_listmailbox($stream, $connstr, $this->_params['rmailbox'])) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Checks that the remote server lives.
     *
     * @access private
     *
     * @return mixed  Returns true on success or PEAR_Error on failure.
     */
    function _srvLives()
    {
        $mailserver = @fsockopen($this->_params['server'], $this->_params['port'], $errno, $errstr);
        if (!$mailserver) {
            return PEAR::raiseError(_("Can't connect to the Mail Server: ") . $errstr);
        } else {
            fclose($mailserver);
        }

        return true;
    }

    /**
     * Attempts to connect to the mail server
     *
     * @access private
     *
     * @return mixed  Returns an IMAP Stream or PEAR_Error on failure.
     */
    function _connect()
    {
        $res = $this->_sanity();
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $res = $this->_srvLives();
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $server_string = '{' . $this->_params['server'] . ':' . $this->_params['port'] . '/' . String::lower($this->_params['protocol']) . '}';

        if ($this->_params['base_protocol'] == 'IMAP') {
            $server_string .= $this->_params['rmailbox'];
        }

        $stream = @imap_open($server_string , $this->_params['username'], $this->_params['password']);

        if ($stream === false) {
            return PEAR::raiseError(_("Connection to server failed"));
        }

        return $stream;
    }

    /**
     * Gets the mailbody and calls the custom filter function.
     * Remove bare newlines and sets message color.
     *
     * @access private
     *
     * @param resource $stream  IMAP connection stream.
     * @param integer $index    Message to fetch.
     *
     * @return string  Corrected mail content.
     */
    function _getMailMessage($stream, $index)
    {
        global $conf;

        /* Get the message. */
        $mail_source = @imap_fetchheader($stream, $index);

        $acctcolor = $this->_params['acctcolor'];
        if (!empty($acctcolor)) {
            $mail_source = rtrim($mail_source) . "\nX-color: $acctcolor\n";
        }

        $mail_source .= "\n" . @imap_body($stream, $index, FT_PEEK);

        /* If there is a user defined function, call it with the current
           message as an argument. */
        if ($conf['hooks']['fetchmail_filter']) {
            include_once HORDE_BASE . '/config/hooks.php';
            if (function_exists('_imp_hook_fetchmail_filter')) {
                $mail_source = call_user_func('_imp_hook_fetchmail_filter', $mail_source);
            }
        }

        /* Make absolutely sure there are no bare newlines. */
        $mail_source = preg_replace("|([^\r])\n|", "\\1\r\n", $mail_source);
        $mail_source = str_replace("\n\n", "\n\r\n", $mail_source);

        return $mail_source;
    }

    /**
     * Gets the mail using the data in this object.
     *
     * @access public
     *
     * @return mixed  Returns the number of messages retrieved on success.
     *                Returns PEAR_Error on error.
     */
    function getMail()
    {
        global $conf, $imp, $notification;

        $stream = false;
        $protocols = array();

        if ($this->_params['protocol'] == 'POP3 Auto') {
            global $pop3_protocols;
            $protocols =& $pop3_protocols;
        } elseif ($this->_params['protocol'] == 'IMAP Auto') {
            global $imap_protocols;
            $protocols =& $imap_protocols;
        } else {
            $protocols = array($this->_params['protocol']);
        }

        foreach ($protocols as $protocol) {
            $this->_params['protocol'] = $protocol;
            $stream = $this->_connect();
            if (!is_a($stream, 'PEAR_Error')) {
                break;
            }
        }

        if (is_a($stream, 'PEAR_Error')) {
            return $stream;
        }

        if ($this->_params['base_protocol'] == 'IMAP') {
            if (!$this->_remoteMboxExists($stream)) {
                @imap_close($stream);
                return PEAR::raiseError(_("Invalid Remote Mailbox"));
            }
        }

        $num_msgs = @imap_num_msg($stream);
        $newmailbox = IMP::ServerString() . $this->_params['lmailbox'];

        if ($num_msgs > 0) {
            require_once HORDE_BASE . '/lib/MIME.php';
            $this->_numMsgs = 0;
            for ($index = 1; $index <= $num_msgs; $index++) {
                /* Get the flags so we can set it back to unseen */
                list($h) = @imap_fetch_overview($stream, $index);

                if (($this->_params['onlynew'] &&
                     ($h->recent || !$h->seen) &&
                     !$h->deleted) ||
                    (!$this->_params['onlynew'] && !$h->deleted)) {

                    if (($conf['fetchmail']['size_limit'] != 0) &&
                        ($h->size > $conf['fetchmail']['size_limit'])) {
                        $notification->push(sprintf(_("The message \"%s\" from \"%s\" (%d bytes) exeeds fetch size limit."), MIME::Decode($h->subject), MIME::Decode($h->from), $h->size), 'horde.warning');
                    } else {
                        /* Get the complete message. */
                        $mail_source = $this->_getMailMessage($stream, $index);

                        /* Append to the server. */
                        if (@imap_append($imp['stream'], $newmailbox, $mail_source)) {
                            $this->_numMsgs++;

                            /* Remove the mail if 'del' is set. */
                            if ($this->_params['del']) {
                                if (!@imap_delete($stream, $index)) {
                                    @imap_close($stream);
                                    return PEAR::raiseError(_("An error occurred when deleting messages from server"));
                                }
                            }

                            /* Mark message seen if 'markseen' is set. */
                            if (($this->_params['base_protocol'] == 'IMAP') &&
                                ($this->_params['markseen'])) {
                                if (!@imap_setflag_full($stream, $index, "\\Seen")) {
                                    @imap_close($stream);
                                    return PEAR::raiseError(_("An error occurred when setting the messages seen from server"));
                                }
                            }
                        }
                    }
                }
            }

            if ($this->_params['del']) {
                if (!@imap_expunge($stream)) {
                    @imap_close($stream);
                    return PEAR::raiseError(_("An error occurred when expunging deleted messages from the server"));
                }
            }
        }

        @imap_close($stream);

        return $this->_numMsgs;
    }

}


/**
 * Generic fetchmail functions.
 *
 * @author  Nuno Loureiro <nuno@co.sapo.pt>
 * @author  Michael Slusarz <slusarz@bigworm.colorado.edu>
 * @version $Revision: 1.31 $
 * @since   IMP 4.0
 * @package imp
 */
class IMP_Fetchmail {

    /**
     *
     */
    function fetchMail($params, $notify = false)
    {
        global $imap_protocols, $notification;

        $error = '';

        $srv = &new FetchSrv($params);
        $account = &new Account();

        $result = $srv->getMail();
        if ($notify) {
            if (is_a($result, 'PEAR_Error')) {
                $notification->push(_("Fetchmail: ") . $result->getMessage(), 'horde.warning');
            } elseif ($result) {
                $notification->push(_("Fetchmail: ") . sprintf(_("Fetched %d message(s) from %s"), $result, $account->getValue('id')), 'horde.success');
            }
        }

        /* Catch error messages from c-client. */
        imap_errors();

        return $result;
    }

}
