adding own pop client implementation
[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.
8 * @copyright &copy; 2006 The SquirrelMail Project Team
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
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.
39 * @package plugins
40 * @subpackage mail_fetch
41 */
42class mail_fetch {
43 /**
44 * Server name
45 * @var string
46 */
47 var $host = '';
48
49 /**
50 * POP connection port.
51 * Defaults to 110 on plain text connections and to 995 on TLS
52 * @var integer
53 */
54 var $port = 0;
55
56 /**
57 * Connection type
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)
62 * @var integer
63 */
64 var $tls = 0;
65
66 /**
67 * Authentication type
68 *
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
74 * @var integer
75 */
76 var $auth = 1;
77
78 /**
79 * Connection timeout
80 * @var integer
81 */
82 var $timeout = 60;
83
84 /**
85 * Connection resource
86 * @var stream
87 */
88 var $conn = false;
89
90 /**
91 * Server greeting
92 * @var string
93 */
94 var $greeting = '';
95
96 /**
97 * Timestamp (with <> or empty string)
98 * @var string
99 */
100 var $timestamp = '';
101
102 /**
103 * Capabilities (POP3EXT capa)
104 * @var array
105 */
106 var $capabilities = array();
107
108 /**
109 * Error message buffer
110 * @var string
111 */
112 var $error = '';
113
114 /**
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.
118 * @var string
119 */
120 var $response = '';
121
122 /**
123 * Constructor function
124 *
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
133 */
134 function mail_fetch($aParams=array()) {
135 // hostname
136 if (isset($aParams['host'])) {
137 $this->host = $aParams['host'];
138 } else {
139 return $this->set_error('Server name is not set');
140 }
141 // tls
142 if (isset($aParams['tls'])) {
143 $this->tls = (int) $aParams['tls'];
144 }
145 // port
146 if (isset($aParams['port'])) {
147 $this->port = (int) $aParams['port'];
148 }
149 // set default ports
150 if ($this->port == 0) {
151 if ($this->tls===1) {
152 // pops
153 $this->port = 995;
154 } else {
155 // pop3
156 $this->port = 110;
157 }
158 }
159 // timeout
160 if (isset($aParams['timeout'])) {
161 $this->timeout = (int) $aParams['timeout'];
162 }
163 // authentication mech
164 if (isset($aParams['auth'])) {
165 $this->auth = (int) $aParams['auth'];
166 }
167
168 // open connection
169 $this->open();
170 }
171
172 // Generic methods to handle connection and login operations.
173
174 /**
175 * Opens pop connection
176 *
177 * Command handles TLS and STLS connection differences and fills capabilities
178 * array with RFC2449 CAPA data.
179 * @return boolean
180 */
181 function open() {
182 if ($this->conn) {
183 return true;
184 }
185
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.');
189 }
190 $target = 'tls://' . $this->host;
191 } else {
192 $target = $this->host;
193 }
194
195 $this->conn = @fsockopen($target, $this->port, $errno, $errstr, $this->timeout);
196
197 if (!$this->conn) {
198 $error = sprintf('Error %d: ',$errno) . $errstr;
199 return $this->set_error($error);
200 }
201
202 // read greeting
203 $this->greeting = trim(fgets($this->conn));
204
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];
210 }
211
212 /**
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.
217 */
218 if ($this->auth > 3 || $this->tls===2) {
219 $this->command_capa();
220 }
221
222 // STARTTLS support
223 if ($this->tls===2) {
224 return $this->command_stls();
225 }
226
227 return true;
228 }
229
230 /**
231 * Reads first response line and checks it for errors
232 * @return boolean true = success, false = failure, check error buffer
233 */
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);
240 return true;
241 } else {
242 $this->response = trim($line);
243 return $this->set_error('Unknown response');
244 }
245 }
246
247 /**
248 * Standard SquirrelMail function copied to class in order to make class
249 * independent from SquirrelMail.
250 */
251 function check_php_version ($a = '0', $b = '0', $c = '0') {
252 return version_compare ( PHP_VERSION, "$a.$b.$c", 'ge' );
253 }
254
255 /**
256 * Generic login wrapper
257 *
258 * Connection is not closed on login error (unless POP server drops
259 * connection)
260 * @param string $username
261 * @param string $password
262 * @return boolean
263 */
264 function login($username,$password) {
265 $ret = false;
266
267 // RFC1939 APOP authentication
268 if (! $ret && $this->auth & 2) {
269 // clean error buffer
270 $this->error = '';
271 // APOP login
272 $ret = $this->command_apop($username,$password);
273 }
274
275 // RFC1939 USER authentication
276 if (! $ret && $this->auth & 1) {
277 // clean error buffer
278 $this->error = '';
279 // Default to USER/PASS login
280 if (! $this->command_user($username)) {
281 // error is already in error buffer
282 $ret = false;
283 } else {
284 $ret = $this->command_pass($password);
285 }
286 }
287 return $ret;
288 }
289
290 /**
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
295 */
296 function set_error($error,$close_conn=false) {
297 $this->error = $error;
298 if ($close_conn) {
299 $this->command_quit();
300 }
301 return false;
302 }
303
304 // POP (rfc 1939) commands
305
306 /**
307 * Gets mailbox status
308 * array with 'count' and 'size' keys
309 * @return mixed array or boolean false
310 */
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]);
317 } else {
318 return $this->set_error('stat command failed');
319 }
320 }
321
322 /**
323 * List mailbox messages
324 * @param integer $msg
325 * @return mixed array with message ids (keys) and sizes (values) or boolean false
326 */
327 function command_list($msg='') {
328 fwrite($this->conn,"LIST $msg\r\n");
329
330 if($this->check_response()) {
331 $ret = array();
332 if (!empty($msg)) {
333 list($ok,$msg_id,$size) = explode(' ',trim($this->response));
334 $ret[$msg_id] = $size;
335 } else {
336 while($line = fgets($this->conn)) {
337 if (trim($line)=='.') {
338 break;
339 } else {
340 list($msg_id,$size) = explode(' ',trim($line));
341 $ret[$msg_id] = $size;
342 }
343 }
344 }
345 return $ret;
346 } else {
347 return false;
348 }
349 }
350
351 /**
352 * Gets message text
353 * @param integer $msg message id
354 * @return mixed rfc822 message (CRLF line endings) or boolean false
355 */
356 function command_retr($msg) {
357 fwrite($this->conn,"RETR $msg\r\n");
358
359 if($this->check_response()) {
360 $ret = '';
361 while($line = fgets($this->conn)) {
362 if (trim($line)=='.') {
363 break;
364 } else {
365 $ret.= $line;
366 }
367 }
368 return $ret;
369 } else {
370 return false;
371 }
372 }
373
374 /**
375 * @param integer $msg
376 * @return boolean
377 */
378 function command_dele($msg) {
379 fwrite($this->conn,"DELE $msg\r\n");
380 return $this->check_response();
381 }
382
383 /**
384 * POP noop command
385 * @return boolean
386 */
387 function command_noop() {
388 fwrite($this->conn,"NOOP\r\n");
389 return $this->check_response();
390 }
391
392 /**
393 * Resets message state
394 * @return boolean
395 */
396 function command_rset() {
397 fwrite($this->conn,"RSET\r\n");
398 return $this->check_response();
399 }
400
401 /**
402 * Closes POP connection
403 */
404 function command_quit() {
405 fwrite($this->conn,"QUIT\r\n");
406 fclose($this->conn);
407 $this->conn = false;
408 }
409
410 // Optional RFC1939 commands
411
412 /**
413 * Gets message headers and $n of body lines.
414 *
415 * Command is optional and not required by rfc1939
416 * @param integer $msg
417 * @param integer $n
418 * @return string or boolean false
419 */
420 function command_top($msg,$n) {
421 fwrite($this->conn,"TOP $msg $n\r\n");
422
423 if($this->check_response()) {
424 $ret = '';
425 while($line = fgets($this->conn)) {
426 if (trim($line)=='.') {
427 break;
428 } else {
429 $ret.= $line;
430 }
431 }
432 return $ret;
433 } else {
434 return false;
435 }
436 }
437
438 /**
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)
443 * or boolean false
444 */
445 function command_uidl($msg='') {
446 fwrite($this->conn,"UIDL $msg\r\n");
447 if($this->check_response()) {
448 $ids = array();
449 if (!empty($msg)) {
450 list($ok,$msg_id,$unique_id) = explode(' ',trim($this->response));
451 $ids[$msg_id] = $unique_id;
452 } else {
453 while($line = fgets($this->conn)) {
454 if (trim($line)=='.') {
455 break;
456 } else {
457 list($msg_id,$unique_id) = explode(' ',trim($line));
458 $ids[$msg_id] = $unique_id;
459 }
460 }
461 }
462 return $ids;
463 } else {
464 return false;
465 }
466 }
467
468 /**
469 * USER authentication (username command)
470 *
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.
475 */
476 function command_user($username) {
477 fwrite($this->conn,"USER $username\r\n");
478 return $this->check_response();
479 }
480
481 /**
482 * USER authentication (password command)
483 *
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.
488 */
489 function command_pass($password) {
490 fwrite($this->conn,"PASS $password\r\n");
491 return $this->check_response();
492 }
493
494 /**
495 * APOP authentication
496 *
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.
504 */
505 function command_apop($username,$password) {
506 if (empty($this->timestamp)) {
507 return $this->set_error('APOP is not supported by selected server.');
508 }
509 $digest = md5($this->timestamp . $password);
510
511 fwrite($this->conn,"APOP $username $digest\r\n");
512 return $this->check_response();
513 }
514
515 // RFC2449 POP3EXT
516
517 /**
518 * Checks pop server capabilities
519 *
520 * RFC2449. Fills capabilities array.
521 * @return void
522 */
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)=='.') {
530 break;
531 } else {
532 $this->capabilities[] = trim($line);
533 }
534 }
535 } else {
536 // if capa fails, error buffer contains error message.
537 // Clean error buffer,
538 // if POP3EXT is not supported, capability array will be empty
539 $this->error = '';
540 }
541 }
542
543 // RFC2595 STARTTLS
544
545 /**
546 * RFC 2595 POP STARTTLS support
547 * @return boolean
548 */
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);
554 }
555 fwrite($this->conn,"STLS\r\n");
556 if (! $this->check_response()) {
557 return false;
558 }
559
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();
564 } else {
565 /** stream_socket_enable_crypto() call failed. */
566 return $this->set_error('Unable to start TLS.',true);
567 }
568 return true;
569 }
570}