Alignment fix
[squirrelmail.git] / plugins / mail_fetch / class.mail_fetch.php
CommitLineData
e52e9926 1<?php
2/**
3 * POP client class
4 *
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.
c0d96801 8 * @copyright 2006-2012 The SquirrelMail Project Team
e52e9926 9 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
10 * @version $Id$
11 * @package plugins
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)
17 */
18
19/**
20 * POP3 client class
21 *
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.
27 *
28 * Basic use:
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
929da10d 34 * 'keep mess on server' functions. Make sure that you handle possible UIDL
35 * command errors.
e52e9926 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.
40 * @package plugins
41 * @subpackage mail_fetch
42 */
43class mail_fetch {
44 /**
45 * Server name
46 * @var string
47 */
48 var $host = '';
49
50 /**
51 * POP connection port.
52 * Defaults to 110 on plain text connections and to 995 on TLS
53 * @var integer
54 */
55 var $port = 0;
56
57 /**
58 * Connection type
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
929da10d 62 * server with POP3EXT and STLS support)
e52e9926 63 * @var integer
64 */
65 var $tls = 0;
66
67 /**
68 * Authentication type
69 *
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
75 * @var integer
76 */
77 var $auth = 1;
78
79 /**
80 * Connection timeout
81 * @var integer
82 */
83 var $timeout = 60;
84
85 /**
86 * Connection resource
87 * @var stream
88 */
89 var $conn = false;
90
91 /**
92 * Server greeting
93 * @var string
94 */
95 var $greeting = '';
96
97 /**
98 * Timestamp (with <> or empty string)
99 * @var string
100 */
101 var $timestamp = '';
102
103 /**
104 * Capabilities (POP3EXT capa)
105 * @var array
106 */
107 var $capabilities = array();
108
109 /**
110 * Error message buffer
111 * @var string
112 */
113 var $error = '';
114
115 /**
929da10d 116 * Response buffer
117 *
e52e9926 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.
121 * @var string
122 */
123 var $response = '';
124
125 /**
126 * Constructor function
127 *
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
136 */
137 function mail_fetch($aParams=array()) {
138 // hostname
139 if (isset($aParams['host'])) {
140 $this->host = $aParams['host'];
141 } else {
142 return $this->set_error('Server name is not set');
143 }
144 // tls
145 if (isset($aParams['tls'])) {
146 $this->tls = (int) $aParams['tls'];
147 }
148 // port
149 if (isset($aParams['port'])) {
150 $this->port = (int) $aParams['port'];
151 }
152 // set default ports
153 if ($this->port == 0) {
154 if ($this->tls===1) {
155 // pops
156 $this->port = 995;
157 } else {
158 // pop3
159 $this->port = 110;
160 }
161 }
162 // timeout
163 if (isset($aParams['timeout'])) {
164 $this->timeout = (int) $aParams['timeout'];
165 }
166 // authentication mech
167 if (isset($aParams['auth'])) {
168 $this->auth = (int) $aParams['auth'];
169 }
170
171 // open connection
172 $this->open();
173 }
174
175 // Generic methods to handle connection and login operations.
176
177 /**
178 * Opens pop connection
179 *
180 * Command handles TLS and STLS connection differences and fills capabilities
181 * array with RFC2449 CAPA data.
182 * @return boolean
183 */
184 function open() {
185 if ($this->conn) {
186 return true;
187 }
188
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.');
192 }
193 $target = 'tls://' . $this->host;
194 } else {
195 $target = $this->host;
196 }
197
198 $this->conn = @fsockopen($target, $this->port, $errno, $errstr, $this->timeout);
199
200 if (!$this->conn) {
201 $error = sprintf('Error %d: ',$errno) . $errstr;
202 return $this->set_error($error);
203 }
204
205 // read greeting
206 $this->greeting = trim(fgets($this->conn));
207
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];
213 }
214
215 /**
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.
220 */
221 if ($this->auth > 3 || $this->tls===2) {
222 $this->command_capa();
223 }
224
225 // STARTTLS support
226 if ($this->tls===2) {
227 return $this->command_stls();
228 }
229
230 return true;
231 }
232
233 /**
234 * Reads first response line and checks it for errors
235 * @return boolean true = success, false = failure, check error buffer
236 */
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);
243 return true;
244 } else {
245 $this->response = trim($line);
246 return $this->set_error('Unknown response');
247 }
248 }
249
250 /**
251 * Standard SquirrelMail function copied to class in order to make class
252 * independent from SquirrelMail.
253 */
254 function check_php_version ($a = '0', $b = '0', $c = '0') {
255 return version_compare ( PHP_VERSION, "$a.$b.$c", 'ge' );
256 }
257
258 /**
259 * Generic login wrapper
260 *
261 * Connection is not closed on login error (unless POP server drops
262 * connection)
263 * @param string $username
264 * @param string $password
265 * @return boolean
266 */
267 function login($username,$password) {
268 $ret = false;
269
270 // RFC1939 APOP authentication
271 if (! $ret && $this->auth & 2) {
272 // clean error buffer
273 $this->error = '';
274 // APOP login
275 $ret = $this->command_apop($username,$password);
276 }
277
278 // RFC1939 USER authentication
279 if (! $ret && $this->auth & 1) {
280 // clean error buffer
281 $this->error = '';
282 // Default to USER/PASS login
283 if (! $this->command_user($username)) {
284 // error is already in error buffer
285 $ret = false;
286 } else {
287 $ret = $this->command_pass($password);
288 }
289 }
290 return $ret;
291 }
292
293 /**
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
298 */
299 function set_error($error,$close_conn=false) {
300 $this->error = $error;
301 if ($close_conn) {
302 $this->command_quit();
303 }
304 return false;
305 }
306
307 // POP (rfc 1939) commands
308
309 /**
310 * Gets mailbox status
311 * array with 'count' and 'size' keys
312 * @return mixed array or boolean false
313 */
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]);
320 } else {
321 return $this->set_error('stat command failed');
322 }
323 }
324
325 /**
326 * List mailbox messages
327 * @param integer $msg
328 * @return mixed array with message ids (keys) and sizes (values) or boolean false
329 */
330 function command_list($msg='') {
929da10d 331 // add space between command and msg_id
332 if(!empty($msg)) $msg = ' ' . $msg;
333
334 fwrite($this->conn,"LIST$msg\r\n");
e52e9926 335
336 if($this->check_response()) {
337 $ret = array();
338 if (!empty($msg)) {
339 list($ok,$msg_id,$size) = explode(' ',trim($this->response));
340 $ret[$msg_id] = $size;
341 } else {
342 while($line = fgets($this->conn)) {
343 if (trim($line)=='.') {
344 break;
345 } else {
346 list($msg_id,$size) = explode(' ',trim($line));
347 $ret[$msg_id] = $size;
348 }
349 }
350 }
351 return $ret;
352 } else {
353 return false;
354 }
355 }
356
357 /**
358 * Gets message text
359 * @param integer $msg message id
360 * @return mixed rfc822 message (CRLF line endings) or boolean false
361 */
362 function command_retr($msg) {
363 fwrite($this->conn,"RETR $msg\r\n");
364
365 if($this->check_response()) {
366 $ret = '';
367 while($line = fgets($this->conn)) {
7cea1608 368 if ($line == ".\r\n") {
e52e9926 369 break;
7cea1608 370 } elseif ( $line{0} == '.' ) {
371 $ret .= substr($line,1);
e52e9926 372 } else {
373 $ret.= $line;
374 }
375 }
376 return $ret;
377 } else {
378 return false;
379 }
380 }
381
382 /**
383 * @param integer $msg
384 * @return boolean
385 */
386 function command_dele($msg) {
387 fwrite($this->conn,"DELE $msg\r\n");
388 return $this->check_response();
389 }
390
391 /**
392 * POP noop command
393 * @return boolean
394 */
395 function command_noop() {
396 fwrite($this->conn,"NOOP\r\n");
397 return $this->check_response();
398 }
399
400 /**
401 * Resets message state
402 * @return boolean
403 */
404 function command_rset() {
405 fwrite($this->conn,"RSET\r\n");
406 return $this->check_response();
407 }
408
409 /**
410 * Closes POP connection
411 */
412 function command_quit() {
413 fwrite($this->conn,"QUIT\r\n");
414 fclose($this->conn);
415 $this->conn = false;
416 }
417
418 // Optional RFC1939 commands
419
420 /**
421 * Gets message headers and $n of body lines.
422 *
423 * Command is optional and not required by rfc1939
424 * @param integer $msg
425 * @param integer $n
426 * @return string or boolean false
427 */
428 function command_top($msg,$n) {
429 fwrite($this->conn,"TOP $msg $n\r\n");
430
431 if($this->check_response()) {
432 $ret = '';
433 while($line = fgets($this->conn)) {
434 if (trim($line)=='.') {
435 break;
436 } else {
437 $ret.= $line;
438 }
439 }
440 return $ret;
441 } else {
442 return false;
443 }
444 }
445
446 /**
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)
451 * or boolean false
452 */
453 function command_uidl($msg='') {
929da10d 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");
e52e9926 458 if($this->check_response()) {
459 $ids = array();
460 if (!empty($msg)) {
461 list($ok,$msg_id,$unique_id) = explode(' ',trim($this->response));
929da10d 462 $ids[$msg_id] = "$unique_id";
e52e9926 463 } else {
464 while($line = fgets($this->conn)) {
465 if (trim($line)=='.') {
466 break;
467 } else {
468 list($msg_id,$unique_id) = explode(' ',trim($line));
929da10d 469 // make sure that unique_id is a string.
470 $ids[$msg_id] = "$unique_id";
e52e9926 471 }
472 }
473 }
474 return $ids;
475 } else {
476 return false;
477 }
478 }
479
480 /**
481 * USER authentication (username command)
482 *
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.
487 */
488 function command_user($username) {
489 fwrite($this->conn,"USER $username\r\n");
490 return $this->check_response();
491 }
492
493 /**
494 * USER authentication (password command)
495 *
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.
500 */
501 function command_pass($password) {
502 fwrite($this->conn,"PASS $password\r\n");
503 return $this->check_response();
504 }
505
506 /**
507 * APOP authentication
508 *
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.
516 */
517 function command_apop($username,$password) {
518 if (empty($this->timestamp)) {
519 return $this->set_error('APOP is not supported by selected server.');
520 }
521 $digest = md5($this->timestamp . $password);
522
523 fwrite($this->conn,"APOP $username $digest\r\n");
524 return $this->check_response();
525 }
526
527 // RFC2449 POP3EXT
528
529 /**
530 * Checks pop server capabilities
531 *
532 * RFC2449. Fills capabilities array.
533 * @return void
534 */
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)=='.') {
542 break;
543 } else {
544 $this->capabilities[] = trim($line);
545 }
546 }
547 } else {
548 // if capa fails, error buffer contains error message.
549 // Clean error buffer,
550 // if POP3EXT is not supported, capability array will be empty
551 $this->error = '';
552 }
553 }
554
555 // RFC2595 STARTTLS
556
557 /**
558 * RFC 2595 POP STARTTLS support
559 * @return boolean
560 */
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);
929da10d 564 } elseif (! in_array('STLS',$this->capabilities)) {
e52e9926 565 return $this->set_error('Selected POP3 server does not support STLS.',true);
566 }
567 fwrite($this->conn,"STLS\r\n");
568 if (! $this->check_response()) {
7cea1608 569 $this->command_quit();
570 return false;
e52e9926 571 }
572
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();
577 } else {
578 /** stream_socket_enable_crypto() call failed. */
579 return $this->set_error('Unable to start TLS.',true);
580 }
581 return true;
582 }
583}