adding own pop client implementation
authortokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Fri, 25 Aug 2006 14:29:15 +0000 (14:29 +0000)
committertokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Fri, 25 Aug 2006 14:29:15 +0000 (14:29 +0000)
Original POP3 class is licensed under some 'General Artistic License' and requires
submitting all modifications to address that bounces.

According to FSF standard Artistic License is not compatible with GPL.
http://www.fsf.org/licensing/licenses/index_html#ArtisticLicense

git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@11640 7612ce4b-ef26-0410-bec9-ea0150e637f0

plugins/mail_fetch/class.mail_fetch.php [new file with mode: 0644]

diff --git a/plugins/mail_fetch/class.mail_fetch.php b/plugins/mail_fetch/class.mail_fetch.php
new file mode 100644 (file)
index 0000000..0d18675
--- /dev/null
@@ -0,0 +1,570 @@
+<?php
+/**
+ * POP client class
+ *
+ * Class depends on PHP pcre extension and fsockopen() function. Some features
+ * might require PHP 4.3.0 with OpenSSL or PHP 5.1.0+. Class checks those extra 
+ * dependencies internally, if used function needs it.
+ * @copyright &copy; 2006 The SquirrelMail Project Team
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
+ * @version $Id$
+ * @package plugins
+ * @subpackage mail_fetch
+ * @link http://www.ietf.org/rfc/rfc1939.txt RFC1939
+ * @link http://www.ietf.org/rfc/rfc2449.txt POP3EXT
+ * @link http://www.ietf.org/rfc/rfc2595.txt STARTTLS
+ * @link http://www.ietf.org/rfc/rfc1734.txt AUTH command (unsupported)
+ */
+
+/**
+ * POP3 client class
+ *
+ * POP connection is opened when class is constructed. All command_* methods
+ * execute specific POP commands on server. Most of other methods should be 
+ * used only internally. Only login() method is public. If command returns 
+ * mixed content and you expect message text, ids or something else, make sure
+ * that it is not boolean false.
+ *
+ * Basic use:
+ * 1. create object with connection params, see mail_fetch method.
+ * 2. check error buffer
+ * 3. login($username,$password) - true = login successful, false = login error.
+ * 4. command_stat() - get number of messages
+ * 5. command_list() - get message ids, use command_uidl(), if you implement 
+ * 'keep mess on server' functions.
+ * 6. command_retr($some_message_id) - get message contents
+ * 7. command_dele($some_message_id) - mark message for deletion
+ * 8. command_quit() - close connection. You must close connection in order 
+ *    to delete messages and remove mailbox lock.
+ * @package plugins
+ * @subpackage mail_fetch
+ */
+class mail_fetch {
+    /**
+     * Server name
+     * @var string
+     */
+    var $host = '';
+
+    /**
+     * POP connection port.
+     * Defaults to 110 on plain text connections and to 995 on TLS
+     * @var integer
+     */
+    var $port = 0;
+
+    /**
+     * Connection type
+     * 0 - plain text (default)
+     * 1 - tls (php 4.3 and openssl extension requirement)
+     * 2 - stls (stream_socket_enable_crypto() requirement. PHP 5.1.0, POP3
+     *     server with POP3EXT and STLS support) (untested)
+     * @var integer
+     */
+    var $tls = 0;
+
+    /**
+     * Authentication type
+     *
+     * Bitwise variable. If variable covers more than one authentication method,
+     * login() tries to use all of them until first successful auth.
+     * 1 - user/pass (rfc1939, default)
+     * 2 - apop (rfc1939, timestamp must be present in greeting)
+     * 3 - apop or user/pass
+     * @var integer
+     */
+    var $auth = 1;
+
+    /**
+     * Connection timeout
+     * @var integer
+     */
+    var $timeout = 60;
+
+    /**
+     * Connection resource
+     * @var stream
+     */
+    var $conn = false;
+
+    /**
+     * Server greeting
+     * @var string
+     */
+    var $greeting = '';
+
+    /**
+     * Timestamp (with <> or empty string)
+     * @var string
+     */
+    var $timestamp = '';
+
+    /**
+     * Capabilities (POP3EXT capa)
+     * @var array
+     */
+    var $capabilities = array();
+
+    /**
+     * Error message buffer
+     * @var string
+     */
+    var $error = '';
+
+    /**
+     * Variable is used to store last positive POP server response 
+     * checked in check_response() method. Used internally to handle
+     * mixed single and multiline command responses.
+     * @var string
+     */
+    var $response = '';
+
+    /**
+     * Constructor function
+     * 
+     * parameter array keys
+     * 'host' - required string, address of server. ip or fqn
+     * 'port' - optional integer, port of server.
+     * 'tls' - optional integer, connection type
+     * 'timeout' - optional integer, connection timeout
+     * 'auth' - optional integer, used authentication mechanism.
+     * See description of class properties
+     * @param array $aParams connection params
+     */
+    function mail_fetch($aParams=array()) {
+        // hostname
+        if (isset($aParams['host'])) {
+            $this->host = $aParams['host'];
+        } else {
+            return $this->set_error('Server name is not set');
+        }
+        // tls
+        if (isset($aParams['tls'])) {
+            $this->tls = (int) $aParams['tls'];
+        }
+        // port
+        if (isset($aParams['port'])) {
+            $this->port = (int) $aParams['port'];
+        }
+        // set default ports
+        if ($this->port == 0) {
+            if ($this->tls===1) {
+                // pops
+                $this->port = 995;
+            } else {
+                // pop3
+                $this->port = 110;
+            }
+        }
+        // timeout
+        if (isset($aParams['timeout'])) {
+            $this->timeout = (int) $aParams['timeout'];
+        }
+        // authentication mech
+        if (isset($aParams['auth'])) {
+            $this->auth = (int) $aParams['auth'];
+        }
+
+        // open connection
+        $this->open();
+    }
+
+    // Generic methods to handle connection and login operations.
+
+    /**
+     * Opens pop connection
+     *
+     * Command handles TLS and STLS connection differences and fills capabilities 
+     * array with RFC2449 CAPA data.
+     * @return boolean
+     */
+    function open() {
+        if ($this->conn) {
+            return true;
+        }
+
+        if ($this->tls===1) {
+            if (! $this->check_php_version(4,3) || ! extension_loaded('openssl')) {
+                return $this->set_error('Used PHP version does not support functions required for POP TLS.');
+            }
+            $target = 'tls://' . $this->host;
+        } else {
+            $target = $this->host;
+        }
+
+        $this->conn = @fsockopen($target, $this->port, $errno, $errstr, $this->timeout);
+
+        if (!$this->conn) {
+            $error = sprintf('Error %d: ',$errno) . $errstr;
+            return $this->set_error($error);
+        }
+
+        // read greeting
+        $this->greeting = trim(fgets($this->conn));
+
+        // check greeting for errors and extract timestamp
+        if (preg_match('/^-ERR (.+)/',$this->greeting,$matches)) {
+            return $this->set_error($matches[1],true);
+        } elseif (preg_match('/^\+OK.+(<.+>)/',$this->greeting,$matches)) {
+            $this->timestamp = $matches[1];
+        }
+
+        /**
+         * fill capability only when connection uses some non-rfc1939
+         * authentication type (currently unsupported) or STARTTLS.
+         * Command is not part of rfc1939 and we don't have to use it 
+         * in simple POP connection.
+         */
+        if ($this->auth > 3 || $this->tls===2) {
+            $this->command_capa();
+        }
+
+        // STARTTLS support
+        if ($this->tls===2) {
+            return $this->command_stls();
+        }
+
+        return true;
+    }
+
+    /**
+     * Reads first response line and checks it for errors
+     * @return boolean true = success, false = failure, check error buffer
+     */
+    function check_response() {
+        $line = fgets($this->conn);
+        if (preg_match('/^-ERR (.+)/',$line,$matches)) {
+            return $this->set_error($matches[1]);
+        } elseif (preg_match('/^\+OK/',$line)) {
+            $this->response = trim($line);
+            return true;
+        } else {
+            $this->response = trim($line);
+            return $this->set_error('Unknown response');
+        }
+    }
+
+    /**
+     * Standard SquirrelMail function copied to class in order to make class 
+     * independent from SquirrelMail.
+     */
+    function check_php_version ($a = '0', $b = '0', $c = '0') {
+        return version_compare ( PHP_VERSION, "$a.$b.$c", 'ge' );
+    }
+
+    /**
+     * Generic login wrapper
+     *
+     * Connection is not closed on login error (unless POP server drops 
+     * connection)
+     * @param string $username
+     * @param string $password
+     * @return boolean
+     */
+    function login($username,$password) {
+        $ret = false;
+
+        // RFC1939 APOP authentication
+        if (! $ret && $this->auth & 2) {
+            // clean error buffer
+            $this->error = '';
+            // APOP login
+            $ret = $this->command_apop($username,$password);
+        }
+
+        // RFC1939 USER authentication
+        if (! $ret && $this->auth & 1) {
+            // clean error buffer
+            $this->error = '';
+            // Default to USER/PASS login
+            if (! $this->command_user($username)) {
+                // error is already in error buffer
+                $ret = false;
+            } else {
+                $ret = $this->command_pass($password);
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * Sets error in error buffer and returns boolean false
+     * @param string $error Error message
+     * @param boolean $close_conn Do we have to close connection
+     * @return boolean false
+     */
+    function set_error($error,$close_conn=false) {
+        $this->error = $error;
+        if ($close_conn) {
+            $this->command_quit();
+        }
+        return false;
+    }
+
+    // POP (rfc 1939) commands
+
+    /**
+     * Gets mailbox status
+     * array with 'count' and 'size' keys
+     * @return mixed array or boolean false
+     */
+    function command_stat() {
+         fwrite($this->conn,"STAT\r\n");
+         $response = fgets($this->conn);
+         if (preg_match('/^\+OK (\d+) (\d+)/',$response,$matches)) {
+            return array('count' => $matches[1],
+                         'size'  => $matches[2]);
+        } else {
+            return $this->set_error('stat command failed');
+        }
+    }
+
+    /**
+     * List mailbox messages
+     * @param integer $msg
+     * @return mixed array with message ids (keys) and sizes (values) or boolean false
+     */
+    function command_list($msg='') {
+        fwrite($this->conn,"LIST $msg\r\n");
+        
+        if($this->check_response()) {
+            $ret = array();
+            if (!empty($msg)) {
+                list($ok,$msg_id,$size) = explode(' ',trim($this->response));
+                $ret[$msg_id] = $size;
+            } else {
+                while($line = fgets($this->conn)) {
+                    if (trim($line)=='.') {
+                        break;
+                    } else {
+                        list($msg_id,$size) = explode(' ',trim($line));
+                        $ret[$msg_id] = $size;
+                    }
+                }
+            }
+            return $ret;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Gets message text
+     * @param integer $msg message id
+     * @return mixed rfc822 message (CRLF line endings) or boolean false
+     */
+    function command_retr($msg) {
+        fwrite($this->conn,"RETR $msg\r\n");
+        
+        if($this->check_response()) {
+            $ret = '';
+            while($line = fgets($this->conn)) {
+                if (trim($line)=='.') {
+                    break;
+                } else {
+                    $ret.= $line;
+                }
+            }
+            return $ret;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * @param integer $msg
+     * @return boolean
+     */
+    function command_dele($msg) {
+       fwrite($this->conn,"DELE $msg\r\n");
+       return $this->check_response();
+    }
+
+    /**
+     * POP noop command
+     * @return boolean
+     */
+    function command_noop() {
+        fwrite($this->conn,"NOOP\r\n");
+        return $this->check_response();
+    }
+
+    /**
+     * Resets message state
+     * @return boolean
+     */
+    function command_rset() {
+        fwrite($this->conn,"RSET\r\n");
+        return $this->check_response();
+    }
+
+    /**
+     * Closes POP connection
+     */
+    function command_quit() {
+        fwrite($this->conn,"QUIT\r\n");
+        fclose($this->conn);
+        $this->conn = false;
+    }
+
+    // Optional RFC1939 commands
+
+    /**
+     * Gets message headers and $n of body lines.
+     *
+     * Command is optional and not required by rfc1939
+     * @param integer $msg
+     * @param integer $n
+     * @return string or boolean false
+     */
+    function command_top($msg,$n) {
+        fwrite($this->conn,"TOP $msg $n\r\n");
+        
+        if($this->check_response()) {
+            $ret = '';
+            while($line = fgets($this->conn)) {
+                if (trim($line)=='.') {
+                    break;
+                } else {
+                    $ret.= $line;
+                }
+            }
+            return $ret;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Gets unique message ids
+     * Command is optional and not required by rfc1939
+     * @param integer $msg message id
+     * @return mixed array with message ids (keys) and unique ids (values) 
+     * or boolean false
+     */
+    function command_uidl($msg='') {
+        fwrite($this->conn,"UIDL $msg\r\n");
+        if($this->check_response()) {
+            $ids = array();
+            if (!empty($msg)) {
+                list($ok,$msg_id,$unique_id) = explode(' ',trim($this->response));
+                $ids[$msg_id] = $unique_id;
+            } else {
+                while($line = fgets($this->conn)) {
+                    if (trim($line)=='.') {
+                        break;
+                    } else {
+                        list($msg_id,$unique_id) = explode(' ',trim($line));
+                        $ids[$msg_id] = $unique_id;
+                    }
+                }
+            }
+            return $ids;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * USER authentication (username command)
+     * 
+     * Command is optional and not required by rfc1939. If command 
+     * is successful, pass command must be executed after it.
+     * @param string $username
+     * @return boolean true = success, false = failure.
+     */
+    function command_user($username) {
+        fwrite($this->conn,"USER $username\r\n");
+        return $this->check_response();
+    }
+
+    /**
+     * USER authentication (password command)
+     *
+     * Command is optional and not required by rfc1939. Requires
+     * successful user command.
+     * @param string $password
+     * @return boolean true = success, false = failure.
+     */
+    function command_pass($password) {
+        fwrite($this->conn,"PASS $password\r\n");
+        return $this->check_response();
+    }
+
+    /**
+     * APOP authentication
+     *
+     * Command is optional and not required by rfc1939. APOP support 
+     * requires plain text passwords stored on server and some servers 
+     * don't support it. Standard qmail pop3d declares apop support 
+     * without checking if checkpassword supports it.
+     * @param string $username
+     * @param string $password
+     * @return boolean true = success, false = failure.
+     */
+    function command_apop($username,$password) {
+        if (empty($this->timestamp)) {
+            return $this->set_error('APOP is not supported by selected server.');
+        }
+        $digest = md5($this->timestamp . $password);
+
+        fwrite($this->conn,"APOP $username $digest\r\n");
+        return $this->check_response();
+    }
+
+    // RFC2449 POP3EXT
+
+    /**
+     * Checks pop server capabilities
+     * 
+     * RFC2449. Fills capabilities array.
+     * @return void
+     */
+    function command_capa() {
+        fwrite($this->conn,"CAPA\r\n");
+        if ($this->check_response()) {
+            // reset array. capabilities depend on authorization state
+            $this->capabilities = array();
+            while($line = fgets($this->conn)) {
+                if (trim($line)=='.') {
+                    break;
+                } else {
+                    $this->capabilities[] = trim($line);
+                }
+            }
+        } else {
+            // if capa fails, error buffer contains error message.
+            // Clean error buffer,
+            // if POP3EXT is not supported, capability array will be empty
+            $this->error = '';
+        }
+    }
+
+    // RFC2595 STARTTLS
+
+    /**
+     * RFC 2595 POP STARTTLS support
+     * @return boolean
+     */
+    function command_stls() {
+        if (! function_exists('stream_socket_enable_crypto')) {
+            return $this->set_error('Used PHP version does not support functions required for POP STLS.',true);
+        } elseif (in_array('STLS',$this->capabilities)) {
+            return $this->set_error('Selected POP3 server does not support STLS.',true);
+        }
+        fwrite($this->conn,"STLS\r\n");
+        if (! $this->check_response()) {
+            return false;
+        }
+
+        if (@stream_socket_enable_crypto($this->conn,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+            // starttls was successful (rfc2595 4. POP3 STARTTLS extension.)
+            // get new CAPA response
+            $this->command_capa();
+        } else {
+            /** stream_socket_enable_crypto() call failed. */
+            return $this->set_error('Unable to start TLS.',true);
+        }
+        return true;
+    }
+}