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-2010 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. Make sure that you handle possible UIDL
36 * 6. command_retr($some_message_id) - get message contents
37 * 7. command_dele($some_message_id) - mark message for deletion
38 * 8. command_quit() - close connection. You must close connection in order
39 * to delete messages and remove mailbox lock.
41 * @subpackage mail_fetch
51 * POP connection port.
52 * Defaults to 110 on plain text connections and to 995 on TLS
59 * 0 - plain text (default)
60 * 1 - tls (php 4.3 and openssl extension requirement)
61 * 2 - stls (stream_socket_enable_crypto() requirement. PHP 5.1.0, POP3
62 * server with POP3EXT and STLS support)
70 * Bitwise variable. If variable covers more than one authentication method,
71 * login() tries to use all of them until first successful auth.
72 * 1 - user/pass (rfc1939, default)
73 * 2 - apop (rfc1939, timestamp must be present in greeting)
74 * 3 - apop or user/pass
98 * Timestamp (with <> or empty string)
104 * Capabilities (POP3EXT capa)
107 var $capabilities = array();
110 * Error message buffer
118 * Variable is used to store last positive POP server response
119 * checked in check_response() method. Used internally to handle
120 * mixed single and multiline command responses.
126 * Constructor function
128 * parameter array keys
129 * 'host' - required string, address of server. ip or fqn
130 * 'port' - optional integer, port of server.
131 * 'tls' - optional integer, connection type
132 * 'timeout' - optional integer, connection timeout
133 * 'auth' - optional integer, used authentication mechanism.
134 * See description of class properties
135 * @param array $aParams connection params
137 function mail_fetch($aParams=array()) {
139 if (isset($aParams['host'])) {
140 $this->host
= $aParams['host'];
142 return $this->set_error('Server name is not set');
145 if (isset($aParams['tls'])) {
146 $this->tls
= (int) $aParams['tls'];
149 if (isset($aParams['port'])) {
150 $this->port
= (int) $aParams['port'];
153 if ($this->port
== 0) {
154 if ($this->tls
===1) {
163 if (isset($aParams['timeout'])) {
164 $this->timeout
= (int) $aParams['timeout'];
166 // authentication mech
167 if (isset($aParams['auth'])) {
168 $this->auth
= (int) $aParams['auth'];
175 // Generic methods to handle connection and login operations.
178 * Opens pop connection
180 * Command handles TLS and STLS connection differences and fills capabilities
181 * array with RFC2449 CAPA data.
189 if ($this->tls
===1) {
190 if (! $this->check_php_version(4,3) ||
! extension_loaded('openssl')) {
191 return $this->set_error('Used PHP version does not support functions required for POP TLS.');
193 $target = 'tls://' . $this->host
;
195 $target = $this->host
;
198 $this->conn
= @fsockopen
($target, $this->port
, $errno, $errstr, $this->timeout
);
201 $error = sprintf('Error %d: ',$errno) . $errstr;
202 return $this->set_error($error);
206 $this->greeting
= trim(fgets($this->conn
));
208 // check greeting for errors and extract timestamp
209 if (preg_match('/^-ERR (.+)/',$this->greeting
,$matches)) {
210 return $this->set_error($matches[1],true);
211 } elseif (preg_match('/^\+OK.+(<.+>)/',$this->greeting
,$matches)) {
212 $this->timestamp
= $matches[1];
216 * fill capability only when connection uses some non-rfc1939
217 * authentication type (currently unsupported) or STARTTLS.
218 * Command is not part of rfc1939 and we don't have to use it
219 * in simple POP connection.
221 if ($this->auth
> 3 ||
$this->tls
===2) {
222 $this->command_capa();
226 if ($this->tls
===2) {
227 return $this->command_stls();
234 * Reads first response line and checks it for errors
235 * @return boolean true = success, false = failure, check error buffer
237 function check_response() {
238 $line = fgets($this->conn
);
239 if (preg_match('/^-ERR (.+)/',$line,$matches)) {
240 return $this->set_error($matches[1]);
241 } elseif (preg_match('/^\+OK/',$line)) {
242 $this->response
= trim($line);
245 $this->response
= trim($line);
246 return $this->set_error('Unknown response');
251 * Standard SquirrelMail function copied to class in order to make class
252 * independent from SquirrelMail.
254 function check_php_version ($a = '0', $b = '0', $c = '0') {
255 return version_compare ( PHP_VERSION
, "$a.$b.$c", 'ge' );
259 * Generic login wrapper
261 * Connection is not closed on login error (unless POP server drops
263 * @param string $username
264 * @param string $password
267 function login($username,$password) {
270 // RFC1939 APOP authentication
271 if (! $ret && $this->auth
& 2) {
272 // clean error buffer
275 $ret = $this->command_apop($username,$password);
278 // RFC1939 USER authentication
279 if (! $ret && $this->auth
& 1) {
280 // clean error buffer
282 // Default to USER/PASS login
283 if (! $this->command_user($username)) {
284 // error is already in error buffer
287 $ret = $this->command_pass($password);
294 * Sets error in error buffer and returns boolean false
295 * @param string $error Error message
296 * @param boolean $close_conn Do we have to close connection
297 * @return boolean false
299 function set_error($error,$close_conn=false) {
300 $this->error
= $error;
302 $this->command_quit();
307 // POP (rfc 1939) commands
310 * Gets mailbox status
311 * array with 'count' and 'size' keys
312 * @return mixed array or boolean false
314 function command_stat() {
315 fwrite($this->conn
,"STAT\r\n");
316 $response = fgets($this->conn
);
317 if (preg_match('/^\+OK (\d+) (\d+)/',$response,$matches)) {
318 return array('count' => $matches[1],
319 'size' => $matches[2]);
321 return $this->set_error('stat command failed');
326 * List mailbox messages
327 * @param integer $msg
328 * @return mixed array with message ids (keys) and sizes (values) or boolean false
330 function command_list($msg='') {
331 // add space between command and msg_id
332 if(!empty($msg)) $msg = ' ' . $msg;
334 fwrite($this->conn
,"LIST$msg\r\n");
336 if($this->check_response()) {
339 list($ok,$msg_id,$size) = explode(' ',trim($this->response
));
340 $ret[$msg_id] = $size;
342 while($line = fgets($this->conn
)) {
343 if (trim($line)=='.') {
346 list($msg_id,$size) = explode(' ',trim($line));
347 $ret[$msg_id] = $size;
359 * @param integer $msg message id
360 * @return mixed rfc822 message (CRLF line endings) or boolean false
362 function command_retr($msg) {
363 fwrite($this->conn
,"RETR $msg\r\n");
365 if($this->check_response()) {
367 while($line = fgets($this->conn
)) {
368 if ($line == ".\r\n") {
370 } elseif ( $line{0} == '.' ) {
371 $ret .= substr($line,1);
383 * @param integer $msg
386 function command_dele($msg) {
387 fwrite($this->conn
,"DELE $msg\r\n");
388 return $this->check_response();
395 function command_noop() {
396 fwrite($this->conn
,"NOOP\r\n");
397 return $this->check_response();
401 * Resets message state
404 function command_rset() {
405 fwrite($this->conn
,"RSET\r\n");
406 return $this->check_response();
410 * Closes POP connection
412 function command_quit() {
413 fwrite($this->conn
,"QUIT\r\n");
418 // Optional RFC1939 commands
421 * Gets message headers and $n of body lines.
423 * Command is optional and not required by rfc1939
424 * @param integer $msg
426 * @return string or boolean false
428 function command_top($msg,$n) {
429 fwrite($this->conn
,"TOP $msg $n\r\n");
431 if($this->check_response()) {
433 while($line = fgets($this->conn
)) {
434 if (trim($line)=='.') {
447 * Gets unique message ids
448 * Command is optional and not required by rfc1939
449 * @param integer $msg message id
450 * @return mixed array with message ids (keys) and unique ids (values)
453 function command_uidl($msg='') {
454 //return $this->set_error('Unsupported command.');
455 // add space between command and msg_id
456 if(!empty($msg)) $msg = ' ' . $msg;
457 fwrite($this->conn
,"UIDL$msg\r\n");
458 if($this->check_response()) {
461 list($ok,$msg_id,$unique_id) = explode(' ',trim($this->response
));
462 $ids[$msg_id] = "$unique_id";
464 while($line = fgets($this->conn
)) {
465 if (trim($line)=='.') {
468 list($msg_id,$unique_id) = explode(' ',trim($line));
469 // make sure that unique_id is a string.
470 $ids[$msg_id] = "$unique_id";
481 * USER authentication (username command)
483 * Command is optional and not required by rfc1939. If command
484 * is successful, pass command must be executed after it.
485 * @param string $username
486 * @return boolean true = success, false = failure.
488 function command_user($username) {
489 fwrite($this->conn
,"USER $username\r\n");
490 return $this->check_response();
494 * USER authentication (password command)
496 * Command is optional and not required by rfc1939. Requires
497 * successful user command.
498 * @param string $password
499 * @return boolean true = success, false = failure.
501 function command_pass($password) {
502 fwrite($this->conn
,"PASS $password\r\n");
503 return $this->check_response();
507 * APOP authentication
509 * Command is optional and not required by rfc1939. APOP support
510 * requires plain text passwords stored on server and some servers
511 * don't support it. Standard qmail pop3d declares apop support
512 * without checking if checkpassword supports it.
513 * @param string $username
514 * @param string $password
515 * @return boolean true = success, false = failure.
517 function command_apop($username,$password) {
518 if (empty($this->timestamp
)) {
519 return $this->set_error('APOP is not supported by selected server.');
521 $digest = md5($this->timestamp
. $password);
523 fwrite($this->conn
,"APOP $username $digest\r\n");
524 return $this->check_response();
530 * Checks pop server capabilities
532 * RFC2449. Fills capabilities array.
535 function command_capa() {
536 fwrite($this->conn
,"CAPA\r\n");
537 if ($this->check_response()) {
538 // reset array. capabilities depend on authorization state
539 $this->capabilities
= array();
540 while($line = fgets($this->conn
)) {
541 if (trim($line)=='.') {
544 $this->capabilities
[] = trim($line);
548 // if capa fails, error buffer contains error message.
549 // Clean error buffer,
550 // if POP3EXT is not supported, capability array will be empty
558 * RFC 2595 POP STARTTLS support
561 function command_stls() {
562 if (! function_exists('stream_socket_enable_crypto')) {
563 return $this->set_error('Used PHP version does not support functions required for POP STLS.',true);
564 } elseif (! in_array('STLS',$this->capabilities
)) {
565 return $this->set_error('Selected POP3 server does not support STLS.',true);
567 fwrite($this->conn
,"STLS\r\n");
568 if (! $this->check_response()) {
569 $this->command_quit();
573 if (@stream_socket_enable_crypto
($this->conn
,true,STREAM_CRYPTO_METHOD_TLS_CLIENT
)) {
574 // starttls was successful (rfc2595 4. POP3 STARTTLS extension.)
575 // get new CAPA response
576 $this->command_capa();
578 /** stream_socket_enable_crypto() call failed. */
579 return $this->set_error('Unable to start TLS.',true);