5 * Class depends on PHP pcre extension and fsockopen() function. Some features
6 * might require PHP 4.3.0 with OpenSSL or PHP 5.1.0+. Class checks those extra
7 * dependencies internally, if used function needs it.
8 * @copyright © 2006 The SquirrelMail Project Team
9 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
12 * @subpackage mail_fetch
13 * @link http://www.ietf.org/rfc/rfc1939.txt RFC1939
14 * @link http://www.ietf.org/rfc/rfc2449.txt POP3EXT
15 * @link http://www.ietf.org/rfc/rfc2595.txt STARTTLS
16 * @link http://www.ietf.org/rfc/rfc1734.txt AUTH command (unsupported)
22 * POP connection is opened when class is constructed. All command_* methods
23 * execute specific POP commands on server. Most of other methods should be
24 * used only internally. Only login() method is public. If command returns
25 * mixed content and you expect message text, ids or something else, make sure
26 * that it is not boolean false.
29 * 1. create object with connection params, see mail_fetch method.
30 * 2. check error buffer
31 * 3. login($username,$password) - true = login successful, false = login error.
32 * 4. command_stat() - get number of messages
33 * 5. command_list() - get message ids, use command_uidl(), if you implement
34 * 'keep mess on server' functions.
35 * 6. command_retr($some_message_id) - get message contents
36 * 7. command_dele($some_message_id) - mark message for deletion
37 * 8. command_quit() - close connection. You must close connection in order
38 * to delete messages and remove mailbox lock.
40 * @subpackage mail_fetch
50 * POP connection port.
51 * Defaults to 110 on plain text connections and to 995 on TLS
58 * 0 - plain text (default)
59 * 1 - tls (php 4.3 and openssl extension requirement)
60 * 2 - stls (stream_socket_enable_crypto() requirement. PHP 5.1.0, POP3
61 * server with POP3EXT and STLS support) (untested)
69 * Bitwise variable. If variable covers more than one authentication method,
70 * login() tries to use all of them until first successful auth.
71 * 1 - user/pass (rfc1939, default)
72 * 2 - apop (rfc1939, timestamp must be present in greeting)
73 * 3 - apop or user/pass
97 * Timestamp (with <> or empty string)
103 * Capabilities (POP3EXT capa)
106 var $capabilities = array();
109 * Error message buffer
115 * Variable is used to store last positive POP server response
116 * checked in check_response() method. Used internally to handle
117 * mixed single and multiline command responses.
123 * Constructor function
125 * parameter array keys
126 * 'host' - required string, address of server. ip or fqn
127 * 'port' - optional integer, port of server.
128 * 'tls' - optional integer, connection type
129 * 'timeout' - optional integer, connection timeout
130 * 'auth' - optional integer, used authentication mechanism.
131 * See description of class properties
132 * @param array $aParams connection params
134 function mail_fetch($aParams=array()) {
136 if (isset($aParams['host'])) {
137 $this->host
= $aParams['host'];
139 return $this->set_error('Server name is not set');
142 if (isset($aParams['tls'])) {
143 $this->tls
= (int) $aParams['tls'];
146 if (isset($aParams['port'])) {
147 $this->port
= (int) $aParams['port'];
150 if ($this->port
== 0) {
151 if ($this->tls
===1) {
160 if (isset($aParams['timeout'])) {
161 $this->timeout
= (int) $aParams['timeout'];
163 // authentication mech
164 if (isset($aParams['auth'])) {
165 $this->auth
= (int) $aParams['auth'];
172 // Generic methods to handle connection and login operations.
175 * Opens pop connection
177 * Command handles TLS and STLS connection differences and fills capabilities
178 * array with RFC2449 CAPA data.
186 if ($this->tls
===1) {
187 if (! $this->check_php_version(4,3) ||
! extension_loaded('openssl')) {
188 return $this->set_error('Used PHP version does not support functions required for POP TLS.');
190 $target = 'tls://' . $this->host
;
192 $target = $this->host
;
195 $this->conn
= @fsockopen
($target, $this->port
, $errno, $errstr, $this->timeout
);
198 $error = sprintf('Error %d: ',$errno) . $errstr;
199 return $this->set_error($error);
203 $this->greeting
= trim(fgets($this->conn
));
205 // check greeting for errors and extract timestamp
206 if (preg_match('/^-ERR (.+)/',$this->greeting
,$matches)) {
207 return $this->set_error($matches[1],true);
208 } elseif (preg_match('/^\+OK.+(<.+>)/',$this->greeting
,$matches)) {
209 $this->timestamp
= $matches[1];
213 * fill capability only when connection uses some non-rfc1939
214 * authentication type (currently unsupported) or STARTTLS.
215 * Command is not part of rfc1939 and we don't have to use it
216 * in simple POP connection.
218 if ($this->auth
> 3 ||
$this->tls
===2) {
219 $this->command_capa();
223 if ($this->tls
===2) {
224 return $this->command_stls();
231 * Reads first response line and checks it for errors
232 * @return boolean true = success, false = failure, check error buffer
234 function check_response() {
235 $line = fgets($this->conn
);
236 if (preg_match('/^-ERR (.+)/',$line,$matches)) {
237 return $this->set_error($matches[1]);
238 } elseif (preg_match('/^\+OK/',$line)) {
239 $this->response
= trim($line);
242 $this->response
= trim($line);
243 return $this->set_error('Unknown response');
248 * Standard SquirrelMail function copied to class in order to make class
249 * independent from SquirrelMail.
251 function check_php_version ($a = '0', $b = '0', $c = '0') {
252 return version_compare ( PHP_VERSION
, "$a.$b.$c", 'ge' );
256 * Generic login wrapper
258 * Connection is not closed on login error (unless POP server drops
260 * @param string $username
261 * @param string $password
264 function login($username,$password) {
267 // RFC1939 APOP authentication
268 if (! $ret && $this->auth
& 2) {
269 // clean error buffer
272 $ret = $this->command_apop($username,$password);
275 // RFC1939 USER authentication
276 if (! $ret && $this->auth
& 1) {
277 // clean error buffer
279 // Default to USER/PASS login
280 if (! $this->command_user($username)) {
281 // error is already in error buffer
284 $ret = $this->command_pass($password);
291 * Sets error in error buffer and returns boolean false
292 * @param string $error Error message
293 * @param boolean $close_conn Do we have to close connection
294 * @return boolean false
296 function set_error($error,$close_conn=false) {
297 $this->error
= $error;
299 $this->command_quit();
304 // POP (rfc 1939) commands
307 * Gets mailbox status
308 * array with 'count' and 'size' keys
309 * @return mixed array or boolean false
311 function command_stat() {
312 fwrite($this->conn
,"STAT\r\n");
313 $response = fgets($this->conn
);
314 if (preg_match('/^\+OK (\d+) (\d+)/',$response,$matches)) {
315 return array('count' => $matches[1],
316 'size' => $matches[2]);
318 return $this->set_error('stat command failed');
323 * List mailbox messages
324 * @param integer $msg
325 * @return mixed array with message ids (keys) and sizes (values) or boolean false
327 function command_list($msg='') {
328 fwrite($this->conn
,"LIST $msg\r\n");
330 if($this->check_response()) {
333 list($ok,$msg_id,$size) = explode(' ',trim($this->response
));
334 $ret[$msg_id] = $size;
336 while($line = fgets($this->conn
)) {
337 if (trim($line)=='.') {
340 list($msg_id,$size) = explode(' ',trim($line));
341 $ret[$msg_id] = $size;
353 * @param integer $msg message id
354 * @return mixed rfc822 message (CRLF line endings) or boolean false
356 function command_retr($msg) {
357 fwrite($this->conn
,"RETR $msg\r\n");
359 if($this->check_response()) {
361 while($line = fgets($this->conn
)) {
362 if (trim($line)=='.') {
375 * @param integer $msg
378 function command_dele($msg) {
379 fwrite($this->conn
,"DELE $msg\r\n");
380 return $this->check_response();
387 function command_noop() {
388 fwrite($this->conn
,"NOOP\r\n");
389 return $this->check_response();
393 * Resets message state
396 function command_rset() {
397 fwrite($this->conn
,"RSET\r\n");
398 return $this->check_response();
402 * Closes POP connection
404 function command_quit() {
405 fwrite($this->conn
,"QUIT\r\n");
410 // Optional RFC1939 commands
413 * Gets message headers and $n of body lines.
415 * Command is optional and not required by rfc1939
416 * @param integer $msg
418 * @return string or boolean false
420 function command_top($msg,$n) {
421 fwrite($this->conn
,"TOP $msg $n\r\n");
423 if($this->check_response()) {
425 while($line = fgets($this->conn
)) {
426 if (trim($line)=='.') {
439 * Gets unique message ids
440 * Command is optional and not required by rfc1939
441 * @param integer $msg message id
442 * @return mixed array with message ids (keys) and unique ids (values)
445 function command_uidl($msg='') {
446 fwrite($this->conn
,"UIDL $msg\r\n");
447 if($this->check_response()) {
450 list($ok,$msg_id,$unique_id) = explode(' ',trim($this->response
));
451 $ids[$msg_id] = $unique_id;
453 while($line = fgets($this->conn
)) {
454 if (trim($line)=='.') {
457 list($msg_id,$unique_id) = explode(' ',trim($line));
458 $ids[$msg_id] = $unique_id;
469 * USER authentication (username command)
471 * Command is optional and not required by rfc1939. If command
472 * is successful, pass command must be executed after it.
473 * @param string $username
474 * @return boolean true = success, false = failure.
476 function command_user($username) {
477 fwrite($this->conn
,"USER $username\r\n");
478 return $this->check_response();
482 * USER authentication (password command)
484 * Command is optional and not required by rfc1939. Requires
485 * successful user command.
486 * @param string $password
487 * @return boolean true = success, false = failure.
489 function command_pass($password) {
490 fwrite($this->conn
,"PASS $password\r\n");
491 return $this->check_response();
495 * APOP authentication
497 * Command is optional and not required by rfc1939. APOP support
498 * requires plain text passwords stored on server and some servers
499 * don't support it. Standard qmail pop3d declares apop support
500 * without checking if checkpassword supports it.
501 * @param string $username
502 * @param string $password
503 * @return boolean true = success, false = failure.
505 function command_apop($username,$password) {
506 if (empty($this->timestamp
)) {
507 return $this->set_error('APOP is not supported by selected server.');
509 $digest = md5($this->timestamp
. $password);
511 fwrite($this->conn
,"APOP $username $digest\r\n");
512 return $this->check_response();
518 * Checks pop server capabilities
520 * RFC2449. Fills capabilities array.
523 function command_capa() {
524 fwrite($this->conn
,"CAPA\r\n");
525 if ($this->check_response()) {
526 // reset array. capabilities depend on authorization state
527 $this->capabilities
= array();
528 while($line = fgets($this->conn
)) {
529 if (trim($line)=='.') {
532 $this->capabilities
[] = trim($line);
536 // if capa fails, error buffer contains error message.
537 // Clean error buffer,
538 // if POP3EXT is not supported, capability array will be empty
546 * RFC 2595 POP STARTTLS support
549 function command_stls() {
550 if (! function_exists('stream_socket_enable_crypto')) {
551 return $this->set_error('Used PHP version does not support functions required for POP STLS.',true);
552 } elseif (in_array('STLS',$this->capabilities
)) {
553 return $this->set_error('Selected POP3 server does not support STLS.',true);
555 fwrite($this->conn
,"STLS\r\n");
556 if (! $this->check_response()) {
560 if (@stream_socket_enable_crypto
($this->conn
,true,STREAM_CRYPTO_METHOD_TLS_CLIENT
)) {
561 // starttls was successful (rfc2595 4. POP3 STARTTLS extension.)
562 // get new CAPA response
563 $this->command_capa();
565 /** stream_socket_enable_crypto() call failed. */
566 return $this->set_error('Unable to start TLS.',true);