123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- <?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 2006-2025 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. Make sure that you handle possible UIDL
- * command errors.
- * 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)
- * @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 = '';
- /**
- * Response buffer
- *
- * 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='') {
- // add space between command and msg_id
- if(!empty($msg)) $msg = ' ' . $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 ($line == ".\r\n") {
- break;
- } elseif ( $line[0] == '.' ) {
- $ret .= substr($line,1);
- } 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='') {
- //return $this->set_error('Unsupported command.');
- // add space between command and msg_id
- if(!empty($msg)) $msg = ' ' . $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));
- // make sure that unique_id is a string.
- $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()) {
- $this->command_quit();
- 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;
- }
- }
|