From e52e99268e834d38a2b9a8dfbcfee3f300d90b07 Mon Sep 17 00:00:00 2001 From: tokul Date: Fri, 25 Aug 2006 14:29:15 +0000 Subject: [PATCH] adding own pop client implementation 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 | 570 ++++++++++++++++++++++++ 1 file changed, 570 insertions(+) create mode 100644 plugins/mail_fetch/class.mail_fetch.php diff --git a/plugins/mail_fetch/class.mail_fetch.php b/plugins/mail_fetch/class.mail_fetch.php new file mode 100644 index 00000000..0d186752 --- /dev/null +++ b/plugins/mail_fetch/class.mail_fetch.php @@ -0,0 +1,570 @@ + 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; + } +} -- 2.25.1