New get_plugin_version() function, and a couple places to use it.
[squirrelmail.git] / plugins / mail_fetch / class.mail_fetch.php
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. Make sure that you handle possible UIDL
35 * command errors.
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 */
43 class 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
62 * server with POP3EXT and STLS support)
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 /**
116 * Response buffer
117 *
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='') {
331 // add space between command and msg_id
332 if(!empty($msg)) $msg = ' ' . $msg;
333
334 fwrite($this->conn,"LIST$msg\r\n");
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)) {
368 if (trim($line)=='.') {
369 break;
370 } else {
371 $ret.= $line;
372 }
373 }
374 return $ret;
375 } else {
376 return false;
377 }
378 }
379
380 /**
381 * @param integer $msg
382 * @return boolean
383 */
384 function command_dele($msg) {
385 fwrite($this->conn,"DELE $msg\r\n");
386 return $this->check_response();
387 }
388
389 /**
390 * POP noop command
391 * @return boolean
392 */
393 function command_noop() {
394 fwrite($this->conn,"NOOP\r\n");
395 return $this->check_response();
396 }
397
398 /**
399 * Resets message state
400 * @return boolean
401 */
402 function command_rset() {
403 fwrite($this->conn,"RSET\r\n");
404 return $this->check_response();
405 }
406
407 /**
408 * Closes POP connection
409 */
410 function command_quit() {
411 fwrite($this->conn,"QUIT\r\n");
412 fclose($this->conn);
413 $this->conn = false;
414 }
415
416 // Optional RFC1939 commands
417
418 /**
419 * Gets message headers and $n of body lines.
420 *
421 * Command is optional and not required by rfc1939
422 * @param integer $msg
423 * @param integer $n
424 * @return string or boolean false
425 */
426 function command_top($msg,$n) {
427 fwrite($this->conn,"TOP $msg $n\r\n");
428
429 if($this->check_response()) {
430 $ret = '';
431 while($line = fgets($this->conn)) {
432 if (trim($line)=='.') {
433 break;
434 } else {
435 $ret.= $line;
436 }
437 }
438 return $ret;
439 } else {
440 return false;
441 }
442 }
443
444 /**
445 * Gets unique message ids
446 * Command is optional and not required by rfc1939
447 * @param integer $msg message id
448 * @return mixed array with message ids (keys) and unique ids (values)
449 * or boolean false
450 */
451 function command_uidl($msg='') {
452 //return $this->set_error('Unsupported command.');
453 // add space between command and msg_id
454 if(!empty($msg)) $msg = ' ' . $msg;
455 fwrite($this->conn,"UIDL$msg\r\n");
456 if($this->check_response()) {
457 $ids = array();
458 if (!empty($msg)) {
459 list($ok,$msg_id,$unique_id) = explode(' ',trim($this->response));
460 $ids[$msg_id] = "$unique_id";
461 } else {
462 while($line = fgets($this->conn)) {
463 if (trim($line)=='.') {
464 break;
465 } else {
466 list($msg_id,$unique_id) = explode(' ',trim($line));
467 // make sure that unique_id is a string.
468 $ids[$msg_id] = "$unique_id";
469 }
470 }
471 }
472 return $ids;
473 } else {
474 return false;
475 }
476 }
477
478 /**
479 * USER authentication (username command)
480 *
481 * Command is optional and not required by rfc1939. If command
482 * is successful, pass command must be executed after it.
483 * @param string $username
484 * @return boolean true = success, false = failure.
485 */
486 function command_user($username) {
487 fwrite($this->conn,"USER $username\r\n");
488 return $this->check_response();
489 }
490
491 /**
492 * USER authentication (password command)
493 *
494 * Command is optional and not required by rfc1939. Requires
495 * successful user command.
496 * @param string $password
497 * @return boolean true = success, false = failure.
498 */
499 function command_pass($password) {
500 fwrite($this->conn,"PASS $password\r\n");
501 return $this->check_response();
502 }
503
504 /**
505 * APOP authentication
506 *
507 * Command is optional and not required by rfc1939. APOP support
508 * requires plain text passwords stored on server and some servers
509 * don't support it. Standard qmail pop3d declares apop support
510 * without checking if checkpassword supports it.
511 * @param string $username
512 * @param string $password
513 * @return boolean true = success, false = failure.
514 */
515 function command_apop($username,$password) {
516 if (empty($this->timestamp)) {
517 return $this->set_error('APOP is not supported by selected server.');
518 }
519 $digest = md5($this->timestamp . $password);
520
521 fwrite($this->conn,"APOP $username $digest\r\n");
522 return $this->check_response();
523 }
524
525 // RFC2449 POP3EXT
526
527 /**
528 * Checks pop server capabilities
529 *
530 * RFC2449. Fills capabilities array.
531 * @return void
532 */
533 function command_capa() {
534 fwrite($this->conn,"CAPA\r\n");
535 if ($this->check_response()) {
536 // reset array. capabilities depend on authorization state
537 $this->capabilities = array();
538 while($line = fgets($this->conn)) {
539 if (trim($line)=='.') {
540 break;
541 } else {
542 $this->capabilities[] = trim($line);
543 }
544 }
545 } else {
546 // if capa fails, error buffer contains error message.
547 // Clean error buffer,
548 // if POP3EXT is not supported, capability array will be empty
549 $this->error = '';
550 }
551 }
552
553 // RFC2595 STARTTLS
554
555 /**
556 * RFC 2595 POP STARTTLS support
557 * @return boolean
558 */
559 function command_stls() {
560 if (! function_exists('stream_socket_enable_crypto')) {
561 return $this->set_error('Used PHP version does not support functions required for POP STLS.',true);
562 } elseif (! in_array('STLS',$this->capabilities)) {
563 return $this->set_error('Selected POP3 server does not support STLS.',true);
564 }
565 fwrite($this->conn,"STLS\r\n");
566 if (! $this->check_response()) {
567 $this->command_quit();
568 return false;
569 }
570
571 if (@stream_socket_enable_crypto($this->conn,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
572 // starttls was successful (rfc2595 4. POP3 STARTTLS extension.)
573 // get new CAPA response
574 $this->command_capa();
575 } else {
576 /** stream_socket_enable_crypto() call failed. */
577 return $this->set_error('Unable to start TLS.',true);
578 }
579 return true;
580 }
581 }