d03cebac381a37cf36ecc598a700dc085da4ceab
[squirrelmail.git] / functions / imap_general.php
1 <?php
2
3 /**
4 * imap_general.php
5 *
6 * Copyright (c) 1999-2005 The SquirrelMail Project Team
7 * Licensed under the GNU GPL. For full terms see the file COPYING.
8 *
9 * This implements all functions that do general imap functions.
10 *
11 * @version $Id$
12 * @package squirrelmail
13 * @subpackage imap
14 */
15
16 /** Includes.. */
17 require_once(SM_PATH . 'functions/page_header.php');
18 require_once(SM_PATH . 'functions/auth.php');
19
20
21 /**
22 * Generates a new session ID by incrementing the last one used;
23 * this ensures that each command has a unique ID.
24 * @param bool $unique_id (since 1.3.0) controls use of unique
25 * identifiers/message sequence numbers in IMAP commands. See IMAP
26 * rfc 'UID command' chapter.
27 * @return string IMAP session id of the form 'A000'.
28 * @since 1.2.0
29 */
30 function sqimap_session_id($unique_id = FALSE) {
31 static $sqimap_session_id = 1;
32
33 if (!$unique_id) {
34 return( sprintf("A%03d", $sqimap_session_id++) );
35 } else {
36 return( sprintf("A%03d", $sqimap_session_id++) . ' UID' );
37 }
38 }
39
40 /**
41 * Both send a command and accept the result from the command.
42 * This is to allow proper session number handling.
43 * @param resource $imap_stream imap connection resource
44 * @param string $query imap command
45 * @param boolean $handle_errors see sqimap_retrieve_imap_response()
46 * @param mixed $response
47 * @param mixed $message
48 * @param boolean $unique_id (since 1.3.0) controls use of unique
49 * identifiers/message sequence numbers in IMAP commands. See IMAP
50 * rfc 'UID command' chapter.
51 * @return mixed returns false on imap error. displays error message
52 * if imap stream is not available.
53 * @since 1.2.3
54 */
55 function sqimap_run_command_list ($imap_stream, $query, $handle_errors, &$response, &$message, $unique_id = false) {
56 if ($imap_stream) {
57 $sid = sqimap_session_id($unique_id);
58 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
59 $tag_uid_a = explode(' ',trim($sid));
60 $tag = $tag_uid_a[0];
61 $read = sqimap_retrieve_imap_response ($imap_stream, $tag, $handle_errors, $response, $message, $query );
62 /* get the response and the message */
63 $message = $message[$tag];
64 $response = $response[$tag];
65 return $read[$tag];
66 } else {
67 global $squirrelmail_language, $color;
68 set_up_language($squirrelmail_language);
69 require_once(SM_PATH . 'functions/display_messages.php');
70 $string = "<b><font color=\"$color[2]\">\n" .
71 _("ERROR : No available imapstream.") .
72 "</b></font>\n";
73 error_box($string,$color);
74 return false;
75 }
76 }
77
78 /**
79 * @param resource $imap_stream imap connection resource
80 * @param string $query imap command
81 * @param boolean $handle_errors see sqimap_retrieve_imap_response()
82 * @param mixed $response
83 * @param mixed $message
84 * @param boolean $unique_id (since 1.3.0) controls use of unique
85 * identifiers/message sequence numbers in IMAP commands. See IMAP
86 * rfc 'UID command' chapter.
87 * @param mixed $filter (since 1.4.1 and 1.5.0)
88 * @param mixed $outputstream (since 1.4.1 and 1.5.0)
89 * @param mixed $no_return (since 1.4.1 and 1.5.0)
90 * @return mixed returns false on imap error. displays error message
91 * if imap stream is not available.
92 * @since 1.2.3
93 */
94 function sqimap_run_command ($imap_stream, $query, $handle_errors, &$response,
95 &$message, $unique_id = false,$filter=false,
96 $outputstream=false,$no_return=false) {
97 if ($imap_stream) {
98 $sid = sqimap_session_id($unique_id);
99 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
100 $tag_uid_a = explode(' ',trim($sid));
101 $tag = $tag_uid_a[0];
102
103 $read = sqimap_read_data ($imap_stream, $tag, $handle_errors, $response,
104 $message, $query,$filter,$outputstream,$no_return);
105 if (empty($read)) { //Imap server dropped its connection
106 $response = '';
107 $message = '';
108 return false;
109 }
110 /* retrieve the response and the message */
111 $response = $response[$tag];
112 $message = $message[$tag];
113
114 if (!empty($read[$tag])) {
115 return $read[$tag][0];
116 } else {
117 return $read[$tag];
118 }
119 } else {
120 global $squirrelmail_language, $color;
121 set_up_language($squirrelmail_language);
122 require_once(SM_PATH . 'functions/display_messages.php');
123 $string = "<b><font color=\"$color[2]\">\n" .
124 _("ERROR : No available imapstream.") .
125 "</b></font>\n";
126 error_box($string,$color);
127 return false;
128 }
129 }
130
131 /**
132 * @param mixed $new_query
133 * @param string $tag
134 * @param array $aQuery
135 * @param boolean $unique_id
136 * @since 1.5.0
137 */
138 function sqimap_prepare_pipelined_query($new_query,&$tag,&$aQuery,$unique_id) {
139 $sid = sqimap_session_id($unique_id);
140 $tag_uid_a = explode(' ',trim($sid));
141 $tag = $tag_uid_a[0];
142 $query = $sid . ' '.$new_query."\r\n";
143 $aQuery[$tag] = $query;
144 }
145
146 /**
147 * @param stream $imap_stream
148 * @param array $aQueryList
149 * @param boolean $handle_errors
150 * @param array $aServerResponse
151 * @param array $aServerMessage
152 * @param boolean $unique_id
153 * @param mixed $filter see sqimap_retrieve_imap_response()
154 * @param mixed $outputstream see sqimap_retrieve_imap_response()
155 * @param mixed $no_return see sqimap_retrieve_imap_response()
156 * @since 1.5.0
157 */
158 function sqimap_run_pipelined_command ($imap_stream, $aQueryList, $handle_errors,
159 &$aServerResponse, &$aServerMessage, $unique_id = false,
160 $filter=false,$outputstream=false,$no_return=false) {
161 $aResponse = false;
162
163 /*
164 Do not fire all calls at once to the imap-server but split the calls up
165 in portions of $iChunkSize. If we do not do that I think we misbehave as
166 IMAP client or should handle BYE calls if the IMAP-server drops the
167 connection because the number of queries is to large. This isn't tested
168 but a wild guess how it could work in the field.
169
170 After testing it on Exchange 2000 we discovered that a chunksize of 32
171 was quicker then when we raised it to 128.
172 */
173 $iQueryCount = count($aQueryList);
174 $iChunkSize = 32;
175 // array_chunk would also do the job but it's supported from php > 4.2
176 $aQueryChunks = array();
177 $iLoops = floor($iQueryCount / $iChunkSize);
178
179 if ($iLoops * $iChunkSize != $iQueryCount) ++$iLoops;
180
181 if (!function_exists('array_chunk')) { // arraychunk replacement
182 reset($aQueryList);
183 for($i=0;$i<$iLoops;++$i) {
184 for($j=0;$j<$iChunkSize;++$j) {
185 $key = key($aQueryList);
186 $aTmp[$key] = $aQueryList[$key];
187 if (next($aQueryList) === false) break;
188 }
189 $aQueryChunks[] = $aTmp;
190 }
191 } else {
192 $aQueryChunks = array_chunk($aQueryList,$iChunkSize,true);
193 }
194
195 for ($i=0;$i<$iLoops;++$i) {
196 $aQuery = $aQueryChunks[$i];
197 foreach($aQuery as $tag => $query) {
198 fputs($imap_stream,$query);
199 $aResults[$tag] = false;
200 }
201 foreach($aQuery as $tag => $query) {
202 if ($aResults[$tag] == false) {
203 $aReturnedResponse = sqimap_retrieve_imap_response ($imap_stream, $tag,
204 $handle_errors, $response, $message, $query,
205 $filter,$outputstream,$no_return);
206 foreach ($aReturnedResponse as $returned_tag => $aResponse) {
207 if (!empty($aResponse)) {
208 $aResults[$returned_tag] = $aResponse[0];
209 } else {
210 $aResults[$returned_tag] = $aResponse;
211 }
212 $aServerResponse[$returned_tag] = $response[$returned_tag];
213 $aServerMessage[$returned_tag] = $message[$returned_tag];
214 }
215 }
216 }
217 }
218 return $aResults;
219 }
220
221 /**
222 * Custom fgets function: gets a line from the IMAP-server,
223 * no matter how big it may be.
224 * @param stream imap_stream the stream to read from
225 * @return string a line
226 * @since 1.2.8
227 */
228 function sqimap_fgets($imap_stream) {
229 $read = '';
230 $buffer = 4096;
231 $results = '';
232 $offset = 0;
233 while (strpos($results, "\r\n", $offset) === false) {
234 if (!($read = fgets($imap_stream, $buffer))) {
235 /* this happens in case of an error */
236 /* reset $results because it's useless */
237 $results = false;
238 break;
239 }
240 if ( $results != '' ) {
241 $offset = strlen($results) - 1;
242 }
243 $results .= $read;
244 }
245 return $results;
246 }
247
248 /**
249 * @param stream $imap_stream
250 * @param integer $iSize
251 * @param mixed $filter see sqimap_retrieve_imap_response()
252 * @param mixed $outputstream see sqimap_retrieve_imap_response()
253 * @param mixed $no_return see sqimap_retrieve_imap_response()
254 * @return string
255 * @since 1.4.1
256 */
257 function sqimap_fread($imap_stream,$iSize,$filter=false,
258 $outputstream=false, $no_return=false) {
259 if (!$filter || !$outputstream) {
260 $iBufferSize = $iSize;
261 } else {
262 // see php bug 24033. They changed fread behaviour %$^&$%
263 $iBufferSize = 7800; // multiple of 78 in case of base64 decoding.
264 }
265 if ($iSize < $iBufferSize) {
266 $iBufferSize = $iSize;
267 }
268
269 $iRetrieved = 0;
270 $results = '';
271 $sRead = $sReadRem = '';
272 // NB: fread can also stop at end of a packet on sockets.
273 while ($iRetrieved < $iSize) {
274 $sRead = fread($imap_stream,$iBufferSize);
275 $iLength = strlen($sRead);
276 $iRetrieved += $iLength ;
277 $iRemaining = $iSize - $iRetrieved;
278 if ($iRemaining < $iBufferSize) {
279 $iBufferSize = $iRemaining;
280 }
281 if ($sRead == '') {
282 $results = false;
283 break;
284 }
285 if ($sReadRem != '') {
286 $sRead = $sReadRem . $sRead;
287 $sReadRem = '';
288 }
289
290 if ($filter && $sRead != '') {
291 // in case the filter is base64 decoding we return a remainder
292 $sReadRem = $filter($sRead);
293 }
294 if ($outputstream && $sRead != '') {
295 if (is_resource($outputstream)) {
296 fwrite($outputstream,$sRead);
297 } else if ($outputstream == 'php://stdout') {
298 echo $sRead;
299 }
300 }
301 if ($no_return) {
302 $sRead = '';
303 } else {
304 $results .= $sRead;
305 }
306 }
307 return $results;
308 }
309
310
311 /**
312 * Obsolete function, inform plugins that use it
313 * @since 1.1.3
314 * @deprecated (since 1.5.0) use sqimap_run_command or sqimap_run_command_list instead
315 */
316 function sqimap_read_data_list($imap_stream, $tag, $handle_errors,
317 &$response, &$message, $query = '') {
318 global $color, $squirrelmail_language;
319 set_up_language($squirrelmail_language);
320 require_once(SM_PATH . 'functions/display_messages.php');
321 $string = "<b><font color=\"$color[2]\">\n" .
322 _("ERROR : Bad function call.") .
323 "</b><br />\n" .
324 _("Reason:") . ' '.
325 'There is a plugin installed which make use of the <br />' .
326 'SquirrelMail internal function sqimap_read_data_list.<br />'.
327 'Please adapt the installed plugin and let it use<br />'.
328 'sqimap_run_command or sqimap_run_command_list instead<br /><br />'.
329 'The following query was issued:<br />'.
330 htmlspecialchars($query) . '<br />' . "</font><br />\n";
331 error_box($string,$color);
332 echo '</body></html>';
333 exit;
334 }
335
336 /**
337 * Function to display an error related to an IMAP-query.
338 * @param string title the caption of the error box
339 * @param string query the query that went wrong
340 * @param string message_title optional message title
341 * @param string message optional error message
342 * @param string $link an optional link to try again
343 * @return void
344 * @since 1.5.0
345 */
346 function sqimap_error_box($title, $query = '', $message_title = '', $message = '', $link = '')
347 {
348 global $color, $squirrelmail_language;
349
350 set_up_language($squirrelmail_language);
351 require_once(SM_PATH . 'functions/display_messages.php');
352 $string = "<font color=\"$color[2]\"><b>\n" . $title . "</b><br />\n";
353 $cmd = explode(' ',$query);
354 $cmd= strtolower($cmd[0]);
355
356 if ($query != '' && $cmd != 'login')
357 $string .= _("Query:") . ' ' . htmlspecialchars($query) . '<br />';
358 if ($message_title != '')
359 $string .= $message_title;
360 if ($message != '')
361 $string .= htmlspecialchars($message);
362 $string .= "</font><br />\n";
363 if ($link != '')
364 $string .= $link;
365 error_box($string,$color);
366 }
367
368 /**
369 * Reads the output from the IMAP stream. If handle_errors is set to true,
370 * this will also handle all errors that are received. If it is not set,
371 * the errors will be sent back through $response and $message.
372 * @param stream $imap_stream
373 * @param mixed $tag
374 * @param boolean $handle_errors
375 * @param array $response
376 * @param array $message
377 * @param mixed $query
378 * @param mixed $filter
379 * @param mixed $outputstream
380 * @param mixed $no_return
381 * @since 1.5.0
382 */
383 function sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
384 &$response, &$message, $query = '',
385 $filter = false, $outputstream = false, $no_return = false) {
386 global $color, $squirrelmail_language;
387 $read = '';
388 if (!is_array($message)) $message = array();
389 if (!is_array($response)) $response = array();
390 $aResponse = '';
391 $resultlist = array();
392 $data = array();
393 $read = sqimap_fgets($imap_stream);
394 $i = 0;
395 while ($read) {
396 $char = $read{0};
397 switch ($char)
398 {
399 case '+':
400 default:
401 $read = sqimap_fgets($imap_stream);
402 break;
403
404 case $tag{0}:
405 {
406 /* get the command */
407 $arg = '';
408 $i = strlen($tag)+1;
409 $s = substr($read,$i);
410 if (($j = strpos($s,' ')) || ($j = strpos($s,"\n"))) {
411 $arg = substr($s,0,$j);
412 }
413 $found_tag = substr($read,0,$i-1);
414 if ($found_tag) {
415 switch ($arg)
416 {
417 case 'OK':
418 case 'BAD':
419 case 'NO':
420 case 'BYE':
421 case 'PREAUTH':
422 $response[$found_tag] = $arg;
423 $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
424 if (!empty($data)) {
425 $resultlist[] = $data;
426 }
427 $aResponse[$found_tag] = $resultlist;
428 $data = $resultlist = array();
429 if ($found_tag == $tag) {
430 break 3; /* switch switch while */
431 }
432 break;
433 default:
434 /* this shouldn't happen */
435 $response[$found_tag] = $arg;
436 $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
437 if (!empty($data)) {
438 $resultlist[] = $data;
439 }
440 $aResponse[$found_tag] = $resultlist;
441 $data = $resultlist = array();
442 if ($found_tag == $tag) {
443 break 3; /* switch switch while */
444 }
445 }
446 }
447 $read = sqimap_fgets($imap_stream);
448 if ($read === false) { /* error */
449 break 2; /* switch while */
450 }
451 break;
452 } // end case $tag{0}
453
454 case '*':
455 {
456 if (preg_match('/^\*\s\d+\sFETCH/',$read)) {
457 /* check for literal */
458 $s = substr($read,-3);
459 $fetch_data = array();
460 do { /* outer loop, continue until next untagged fetch
461 or tagged reponse */
462 do { /* innerloop for fetching literals. with this loop
463 we prohibid that literal responses appear in the
464 outer loop so we can trust the untagged and
465 tagged info provided by $read */
466 if ($s === "}\r\n") {
467 $j = strrpos($read,'{');
468 $iLit = substr($read,$j+1,-3);
469 $fetch_data[] = $read;
470 $sLiteral = sqimap_fread($imap_stream,$iLit,$filter,$outputstream,$no_return);
471 if ($sLiteral === false) { /* error */
472 break 4; /* while while switch while */
473 }
474 /* backwards compattibility */
475 $aLiteral = explode("\n", $sLiteral);
476 /* release not neaded data */
477 unset($sLiteral);
478 foreach ($aLiteral as $line) {
479 $fetch_data[] = $line ."\n";
480 }
481 /* release not neaded data */
482 unset($aLiteral);
483 /* next fgets belongs to this fetch because
484 we just got the exact literalsize and there
485 must follow data to complete the response */
486 $read = sqimap_fgets($imap_stream);
487 if ($read === false) { /* error */
488 break 4; /* while while switch while */
489 }
490 $fetch_data[] = $read;
491 } else {
492 $fetch_data[] = $read;
493 }
494 /* retrieve next line and check in the while
495 statements if it belongs to this fetch response */
496 $read = sqimap_fgets($imap_stream);
497 if ($read === false) { /* error */
498 break 4; /* while while switch while */
499 }
500 /* check for next untagged reponse and break */
501 if ($read{0} == '*') break 2;
502 $s = substr($read,-3);
503 } while ($s === "}\r\n");
504 $s = substr($read,-3);
505 } while ($read{0} !== '*' &&
506 substr($read,0,strlen($tag)) !== $tag);
507 $resultlist[] = $fetch_data;
508 /* release not neaded data */
509 unset ($fetch_data);
510 } else {
511 $s = substr($read,-3);
512 do {
513 if ($s === "}\r\n") {
514 $j = strrpos($read,'{');
515 $iLit = substr($read,$j+1,-3);
516 $data[] = $read;
517 $sLiteral = fread($imap_stream,$iLit);
518 if ($sLiteral === false) { /* error */
519 $read = false;
520 break 3; /* while switch while */
521 }
522 $data[] = $sLiteral;
523 $data[] = sqimap_fgets($imap_stream);
524 } else {
525 $data[] = $read;
526 }
527 $read = sqimap_fgets($imap_stream);
528 if ($read === false) {
529 break 3; /* while switch while */
530 } else if ($read{0} == '*') {
531 break;
532 }
533 $s = substr($read,-3);
534 } while ($s === "}\r\n");
535 break 1;
536 }
537 break;
538 } // end case '*'
539 } // end switch
540 } // end while
541
542 /* error processing in case $read is false */
543 if ($read === false) {
544 // try to retrieve an untagged bye respons from the results
545 $sResponse = array_pop($data);
546 if ($sResponse !== NULL && strpos($sResponse,'* BYE') !== false) {
547 if (!$handle_errors) {
548 $query = '';
549 }
550 sqimap_error_box(_("ERROR : Imap server closed the connection."), $query, _("Server responded:"),$sResponse);
551 echo '</body></html>';
552 exit;
553 } else if ($handle_errors) {
554 unset($data);
555 sqimap_error_box(_("ERROR : Connection dropped by imap-server."), $query);
556 exit;
557 }
558 }
559
560 /* Set $resultlist array */
561 if (!empty($data)) {
562 //$resultlist[] = $data;
563 }
564 elseif (empty($resultlist)) {
565 $resultlist[] = array();
566 }
567
568 /* Return result or handle errors */
569 if ($handle_errors == false) {
570 return $aResponse;
571 }
572 switch ($response[$tag]) {
573 case 'OK':
574 return $aResponse;
575 break;
576 case 'NO':
577 /* ignore this error from M$ exchange, it is not fatal (aka bug) */
578 if (strstr($message[$tag], 'command resulted in') === false) {
579 sqimap_error_box(_("ERROR : Could not complete request."), $query, _("Reason Given: "), $message[$tag]);
580 echo '</body></html>';
581 exit;
582 }
583 break;
584 case 'BAD':
585 sqimap_error_box(_("ERROR : Bad or malformed request."), $query, _("Server responded: "), $message[$tag]);
586 echo '</body></html>';
587 exit;
588 case 'BYE':
589 sqimap_error_box(_("ERROR : Imap server closed the connection."), $query, _("Server responded: "), $message[$tag]);
590 echo '</body></html>';
591 exit;
592 default:
593 sqimap_error_box(_("ERROR : Unknown imap response."), $query, _("Server responded: "), $message[$tag]);
594 /* the error is displayed but because we don't know the reponse we
595 return the result anyway */
596 return $aResponse;
597 break;
598 }
599 }
600
601 /**
602 * @param stream $imap_stream
603 * @param string $tag_uid
604 * @param boolean $handle_errors
605 * @param array $response
606 * @param array $message
607 * @param mixed $query (since 1.2.5)
608 * @param mixed $filter (since 1.4.1) see sqimap_retrieve_imap_response()
609 * @param mixed $outputstream (since 1.4.1) see sqimap_retrieve_imap_response()
610 * @param mixed $no_return (since 1.4.1) see sqimap_retrieve_imap_response()
611 */
612 function sqimap_read_data ($imap_stream, $tag_uid, $handle_errors,
613 &$response, &$message, $query = '',
614 $filter=false,$outputstream=false,$no_return=false) {
615
616 $tag_uid_a = explode(' ',trim($tag_uid));
617 $tag = $tag_uid_a[0];
618
619 $res = sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
620 $response, $message, $query,$filter,$outputstream,$no_return);
621 return $res;
622 }
623
624 /**
625 * Connects to the IMAP server and returns a resource identifier for use with
626 * the other SquirrelMail IMAP functions. Does NOT login!
627 * @param string server hostname of IMAP server
628 * @param int port port number to connect to
629 * @param bool tls whether to use TLS when connecting.
630 * @return imap-stream resource identifier
631 * @since 1.5.0 (usable only in 1.5.1 or later)
632 */
633 function sqimap_create_stream($server,$port,$tls=false) {
634 global $squirrelmail_language;
635
636 if ($tls == true) {
637 if ((check_php_version(4,3)) and (extension_loaded('openssl'))) {
638 /* Use TLS by prefixing "tls://" to the hostname */
639 $server = 'tls://' . $server;
640 } else {
641 require_once(SM_PATH . 'functions/display_messages.php');
642 logout_error( sprintf(_("Error connecting to IMAP server: %s."), $server).
643 '<br />'.
644 _("TLS is enabled, but this version of PHP does not support TLS sockets, or is missing the openssl extension.").
645 '<br /><br />'.
646 _("Please contact your system administrator and report this error.") );
647 }
648 }
649
650 $imap_stream = @fsockopen($server, $port, $error_number, $error_string, 15);
651
652 /* Do some error correction */
653 if (!$imap_stream) {
654 set_up_language($squirrelmail_language, true);
655 require_once(SM_PATH . 'functions/display_messages.php');
656 logout_error( sprintf(_("Error connecting to IMAP server: %s."), $server).
657 "<br />\r\n$error_number : $error_string<br />\r\n" );
658 exit;
659 }
660 $server_info = fgets ($imap_stream, 1024);
661 return $imap_stream;
662 }
663
664 /**
665 * Logs the user into the imap server. If $hide is set, no error messages
666 * will be displayed. This function returns the imap connection handle.
667 * @param string $username user name
668 * @param string $password encrypted password
669 * @param string $imap_server_address address of imap server
670 * @param integer $imap_port port of imap server
671 * @param boolean $hide controls display connection errors
672 * @return stream
673 */
674 function sqimap_login ($username, $password, $imap_server_address, $imap_port, $hide) {
675 global $color, $squirrelmail_language, $onetimepad, $use_imap_tls,
676 $imap_auth_mech, $sqimap_capabilities;
677
678 if (!isset($onetimepad) || empty($onetimepad)) {
679 sqgetglobalvar('onetimepad' , $onetimepad , SQ_SESSION );
680 }
681 if (!isset($sqimap_capabilities)) {
682 sqgetglobalvar('sqimap_capabilities' , $capability , SQ_SESSION );
683 }
684
685 $host = $imap_server_address;
686 $imap_server_address = sqimap_get_user_server($imap_server_address, $username);
687
688 $imap_stream = sqimap_create_stream($imap_server_address,$imap_port,$use_imap_tls);
689
690 /* Decrypt the password */
691 $password = OneTimePadDecrypt($password, $onetimepad);
692
693 if (($imap_auth_mech == 'cram-md5') OR ($imap_auth_mech == 'digest-md5')) {
694 // We're using some sort of authentication OTHER than plain or login
695 $tag=sqimap_session_id(false);
696 if ($imap_auth_mech == 'digest-md5') {
697 $query = $tag . " AUTHENTICATE DIGEST-MD5\r\n";
698 } elseif ($imap_auth_mech == 'cram-md5') {
699 $query = $tag . " AUTHENTICATE CRAM-MD5\r\n";
700 }
701 fputs($imap_stream,$query);
702 $answer=sqimap_fgets($imap_stream);
703 // Trim the "+ " off the front
704 $response=explode(" ",$answer,3);
705 if ($response[0] == '+') {
706 // Got a challenge back
707 $challenge=$response[1];
708 if ($imap_auth_mech == 'digest-md5') {
709 $reply = digest_md5_response($username,$password,$challenge,'imap',$host);
710 } elseif ($imap_auth_mech == 'cram-md5') {
711 $reply = cram_md5_response($username,$password,$challenge);
712 }
713 fputs($imap_stream,$reply);
714 $read=sqimap_fgets($imap_stream);
715 if ($imap_auth_mech == 'digest-md5') {
716 // DIGEST-MD5 has an extra step..
717 if (substr($read,0,1) == '+') { // OK so far..
718 fputs($imap_stream,"\r\n");
719 $read=sqimap_fgets($imap_stream);
720 }
721 }
722 $results=explode(" ",$read,3);
723 $response=$results[1];
724 $message=$results[2];
725 } else {
726 // Fake the response, so the error trap at the bottom will work
727 $response="BAD";
728 $message='IMAP server does not appear to support the authentication method selected.';
729 $message .= ' Please contact your system administrator.';
730 }
731 } elseif ($imap_auth_mech == 'login') {
732 // Original IMAP login code
733 $query = 'LOGIN "' . quoteimap($username) . '" "' . quoteimap($password) . '"';
734 $read = sqimap_run_command ($imap_stream, $query, false, $response, $message);
735 } elseif ($imap_auth_mech == 'plain') {
736 /***
737 * SASL PLAIN
738 *
739 * RFC 2595 Chapter 6
740 *
741 * The mechanism consists of a single message from the client to the
742 * server. The client sends the authorization identity (identity to
743 * login as), followed by a US-ASCII NUL character, followed by the
744 * authentication identity (identity whose password will be used),
745 * followed by a US-ASCII NUL character, followed by the clear-text
746 * password. The client may leave the authorization identity empty to
747 * indicate that it is the same as the authentication identity.
748 *
749 **/
750 $tag=sqimap_session_id(false);
751 $sasl = (isset($capability['SASL-IR']) && $capability['SASL-IR']) ? true : false;
752 $auth = base64_encode("$username\0$username\0$password");
753 if ($sasl) {
754 // IMAP Extension for SASL Initial Client Response
755 // <draft-siemborski-imap-sasl-initial-response-01b.txt>
756 $query = $tag . " AUTHENTICATE PLAIN $auth\r\n";
757 fputs($imap_stream, $query);
758 $read = sqimap_fgets($imap_stream);
759 } else {
760 $query = $tag . " AUTHENTICATE PLAIN\r\n";
761 fputs($imap_stream, $query);
762 $read=sqimap_fgets($imap_stream);
763 if (substr($read,0,1) == '+') { // OK so far..
764 fputs($imap_stream, "$auth\r\n");
765 $read = sqimap_fgets($imap_stream);
766 }
767 }
768 $results=explode(" ",$read,3);
769 $response=$results[1];
770 $message=$results[2];
771 } else {
772 $response="BAD";
773 $message="Internal SquirrelMail error - unknown IMAP authentication method chosen. Please contact the developers.";
774 }
775
776 /* If the connection was not successful, lets see why */
777 if ($response != 'OK') {
778 if (!$hide) {
779 if ($response != 'NO') {
780 /* "BAD" and anything else gets reported here. */
781 $message = htmlspecialchars($message);
782 set_up_language($squirrelmail_language, true);
783 require_once(SM_PATH . 'functions/display_messages.php');
784 if ($response == 'BAD') {
785 $string = sprintf (_("Bad request: %s")."<br />\r\n", $message);
786 } else {
787 $string = sprintf (_("Unknown error: %s") . "<br />\n", $message);
788 }
789 if (isset($read) && is_array($read)) {
790 $string .= '<br />' . _("Read data:") . "<br />\n";
791 foreach ($read as $line) {
792 $string .= htmlspecialchars($line) . "<br />\n";
793 }
794 }
795 error_box($string,$color);
796 exit;
797 } else {
798 /*
799 * If the user does not log in with the correct
800 * username and password it is not possible to get the
801 * correct locale from the user's preferences.
802 * Therefore, apply the same hack as on the login
803 * screen.
804 *
805 * $squirrelmail_language is set by a cookie when
806 * the user selects language and logs out
807 */
808
809 set_up_language($squirrelmail_language, true);
810 include_once(SM_PATH . 'functions/display_messages.php' );
811 sqsession_destroy();
812 /* terminate the session nicely */
813 sqimap_logout($imap_stream);
814 logout_error( _("Unknown user or password incorrect.") );
815 exit;
816 }
817 } else {
818 exit;
819 }
820 }
821
822 /* Special error case:
823 * Login referrals. The server returns:
824 * ? OK [REFERRAL <imap url>]
825 * Check RFC 2221 for details. Since we do not support login referrals yet
826 * we log the user out.
827 */
828 if ( stristr($message, 'REFERRAL imap') === TRUE ) {
829 sqimap_logout($imap_stream);
830 set_up_language($squirrelmail_language, true);
831 include_once(SM_PATH . 'functions/display_messages.php' );
832 sqsession_destroy();
833 logout_error( _("Your mailbox is not located at this server. Try a different server or consult your system administrator") );
834 exit;
835 }
836
837 return $imap_stream;
838 }
839
840 /**
841 * Simply logs out the IMAP session
842 * @param stream imap_stream the IMAP connection to log out.
843 * @return void
844 */
845 function sqimap_logout ($imap_stream) {
846 /* Logout is not valid until the server returns 'BYE'
847 * If we don't have an imap_ stream we're already logged out */
848 if(isset($imap_stream) && $imap_stream)
849 sqimap_run_command($imap_stream, 'LOGOUT', false, $response, $message);
850 }
851
852 /**
853 * Retreive the CAPABILITY string from the IMAP server.
854 * If capability is set, returns only that specific capability,
855 * else returns array of all capabilities.
856 * @param $imap_stream
857 * @param string $capability (optional since 1.3.0)
858 * @return mixed (string if $capability is set and found,
859 * false, if $capability is set and not found,
860 * array if $capability not set)
861 */
862 function sqimap_capability($imap_stream, $capability='') {
863 global $sqimap_capabilities;
864 if (!is_array($sqimap_capabilities)) {
865 $read = sqimap_run_command($imap_stream, 'CAPABILITY', true, $a, $b);
866
867 $c = explode(' ', $read[0]);
868 for ($i=2; $i < count($c); $i++) {
869 $cap_list = explode('=', $c[$i]);
870 if (isset($cap_list[1])) {
871 // FIX ME. capabilities can occure multiple times.
872 // THREAD=REFERENCES THREAD=ORDEREDSUBJECT
873 $sqimap_capabilities[$cap_list[0]] = $cap_list[1];
874 } else {
875 $sqimap_capabilities[$cap_list[0]] = TRUE;
876 }
877 }
878 }
879 if ($capability) {
880 if (isset($sqimap_capabilities[$capability])) {
881 return $sqimap_capabilities[$capability];
882 } else {
883 return false;
884 }
885 }
886 return $sqimap_capabilities;
887 }
888
889 /**
890 * Returns the delimeter between mailboxes: INBOX/Test, or INBOX.Test
891 * @param stream $imap_stream
892 * @return string
893 */
894 function sqimap_get_delimiter ($imap_stream = false) {
895 global $sqimap_delimiter, $optional_delimiter;
896
897 /* Use configured delimiter if set */
898 if((!empty($optional_delimiter)) && $optional_delimiter != 'detect') {
899 return $optional_delimiter;
900 }
901
902 /* Do some caching here */
903 if (!$sqimap_delimiter) {
904 if (sqimap_capability($imap_stream, 'NAMESPACE')) {
905 /*
906 * According to something that I can't find, this is supposed to work on all systems
907 * OS: This won't work in Courier IMAP.
908 * OS: According to rfc2342 response from NAMESPACE command is:
909 * OS: * NAMESPACE (PERSONAL NAMESPACES) (OTHER_USERS NAMESPACE) (SHARED NAMESPACES)
910 * OS: We want to lookup all personal NAMESPACES...
911 */
912 $read = sqimap_run_command($imap_stream, 'NAMESPACE', true, $a, $b);
913 if (eregi('\\* NAMESPACE +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL)', $read[0], $data)) {
914 if (eregi('^\\( *\\((.*)\\) *\\)', $data[1], $data2)) {
915 $pn = $data2[1];
916 }
917 $pna = explode(')(', $pn);
918 while (list($k, $v) = each($pna)) {
919 $lst = explode('"', $v);
920 if (isset($lst[3])) {
921 $pn[$lst[1]] = $lst[3];
922 } else {
923 $pn[$lst[1]] = '';
924 }
925 }
926 }
927 $sqimap_delimiter = $pn[0];
928 } else {
929 fputs ($imap_stream, ". LIST \"INBOX\" \"\"\r\n");
930 $read = sqimap_read_data($imap_stream, '.', true, $a, $b);
931 $read = $read['.'][0]; //sqimap_read_data() now returns a tag array of response array
932 $quote_position = strpos ($read[0], '"');
933 $sqimap_delimiter = substr ($read[0], $quote_position+1, 1);
934 }
935 }
936 return $sqimap_delimiter;
937 }
938
939 /**
940 * This encodes a mailbox name for use in IMAP commands.
941 * @param string what the mailbox to encode
942 * @return string the encoded mailbox string
943 * @since 1.5.0
944 */
945 function sqimap_encode_mailbox_name($what)
946 {
947 if (ereg("[\"\\\r\n]", $what))
948 return '{' . strlen($what) . "}\r\n" . $what; /* 4.3 literal form */
949 return '"' . $what . '"'; /* 4.3 quoted string form */
950 }
951
952 /**
953 * Gets the number of messages in the current mailbox.
954 *
955 * OBSOLETE use sqimap_status_messages instead.
956 */
957 function sqimap_get_num_messages ($imap_stream, $mailbox) {
958 $read_ary = sqimap_run_command ($imap_stream, 'EXAMINE ' . sqimap_encode_mailbox_name($mailbox), false, $result, $message);
959 for ($i = 0; $i < count($read_ary); $i++) {
960 if (ereg("[^ ]+ +([^ ]+) +EXISTS", $read_ary[$i], $regs)) {
961 return $regs[1];
962 }
963 }
964 return false; //"BUG! Couldn't get number of messages in $mailbox!";
965 }
966 include_once(SM_PATH . 'functions/rfc822address.php');
967
968 /**
969 * OBSOLETE FUNCTION should be removed after mailbox_display,
970 * printMessage function is adapted
971 */
972 function parseAddress($address, $max=0) {
973 $aAddress = parseRFC822Address($address,array('limit'=> $max));
974 /*
975 * Because the expected format of the array element is changed we adapt it now.
976 * This also implies that this function is obsolete and should be removed after the
977 * rest of the source is adapted. See Rfc822Address.php for the new function.
978 */
979 array_walk($aAddress, '_adaptAddress');
980 return $aAddress;
981 }
982
983 /**
984 * OBSOLETE FUNCTION should be removed after mailbox_display,
985 * printMessage function is adapted
986 */
987 function _adaptAddress(&$aAddr,$k) {
988 $sPersonal = (isset($aAddr[SQM_ADDR_PERSONAL]) && $aAddr[SQM_ADDR_PERSONAL]) ?
989 $aAddr[SQM_ADDR_PERSONAL] : '';
990 $sEmail = ($aAddr[SQM_ADDR_HOST]) ?
991 $aAddr[SQM_ADDR_MAILBOX] . '@'.$aAddr[SQM_ADDR_HOST] :
992 $aAddr[SQM_ADDR_MAILBOX];
993 $aAddr = array($sEmail,$sPersonal);
994 }
995
996 /**
997 * Returns the number of unseen messages in this folder.
998 * obsoleted by sqimap_status_messages !
999 */
1000 function sqimap_unseen_messages ($imap_stream, $mailbox) {
1001 $aStatus = sqimap_status_messages($imap_stream,$mailbox,array('UNSEEN'));
1002 return $aStatus['UNSEEN'];
1003 }
1004
1005 /**
1006 * Returns the status items of a mailbox.
1007 * Default it returns MESSAGES,UNSEEN and RECENT
1008 * Supported status items are MESSAGES, UNSEEN, RECENT, UIDNEXT and UIDVALIDITY
1009 */
1010 function sqimap_status_messages ($imap_stream, $mailbox,
1011 $aStatusItems = array('MESSAGES','UNSEEN','RECENT')) {
1012
1013 $aStatusItems = implode(' ',$aStatusItems);
1014 $read_ary = sqimap_run_command ($imap_stream, 'STATUS ' . sqimap_encode_mailbox_name($mailbox) .
1015 " ($aStatusItems)", false, $result, $message);
1016 $i = 0;
1017 $messages = $unseen = $recent = $uidnext = $uidvalidity = false;
1018 $regs = array(false,false);
1019 while (isset($read_ary[$i])) {
1020 if (preg_match('/UNSEEN\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1021 $unseen = $regs[1];
1022 }
1023 if (preg_match('/MESSAGES\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1024 $messages = $regs[1];
1025 }
1026 if (preg_match('/RECENT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1027 $recent = $regs[1];
1028 }
1029 if (preg_match('/UIDNEXT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1030 $uidnext = $regs[1];
1031 }
1032 if (preg_match('/UIDVALIDITY\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1033 $uidvalidity = $regs[1];
1034 }
1035 $i++;
1036 }
1037 return array('MESSAGES' => $messages,
1038 'UNSEEN'=>$unseen,
1039 'RECENT' => $recent,
1040 'UIDNEXT' => $uidnext,
1041 'UIDVALIDITY' => $uidvalidity);
1042 }
1043
1044
1045 /**
1046 * Saves a message to a given folder -- used for saving sent messages
1047 */
1048 function sqimap_append ($imap_stream, $sent_folder, $length) {
1049 fputs ($imap_stream, sqimap_session_id() . ' APPEND ' . sqimap_encode_mailbox_name($sent_folder) . " (\\Seen) \{$length}\r\n");
1050 $tmp = fgets ($imap_stream, 1024);
1051 sqimap_append_checkresponse($tmp, $sent_folder);
1052 }
1053
1054 function sqimap_append_done ($imap_stream, $folder='') {
1055 fputs ($imap_stream, "\r\n");
1056 $tmp = fgets ($imap_stream, 1024);
1057 sqimap_append_checkresponse($tmp, $folder);
1058 }
1059
1060 function sqimap_append_checkresponse($response, $folder) {
1061
1062 if (preg_match("/(.*)(BAD|NO)(.*)$/", $response, $regs)) {
1063 global $squirrelmail_language, $color;
1064 set_up_language($squirrelmail_language);
1065 require_once(SM_PATH . 'functions/display_messages.php');
1066
1067 $reason = $regs[3];
1068 if ($regs[2] == 'NO') {
1069 $string = "<b><font color=\"$color[2]\">\n" .
1070 _("ERROR : Could not append message to") ." $folder." .
1071 "</b><br />\n" .
1072 _("Server responded: ") .
1073 $reason . "<br />\n";
1074 if (preg_match("/(.*)(quota)(.*)$/i", $reason, $regs)) {
1075 $string .= _("Solution: ") .
1076 _("Remove unneccessary messages from your folder and start with your Trash folder.")
1077 ."<br />\n";
1078 }
1079 $string .= "</font>\n";
1080 error_box($string,$color);
1081 } else {
1082 $string = "<b><font color=\"$color[2]\">\n" .
1083 _("ERROR : Bad or malformed request.") .
1084 "</b><br />\n" .
1085 _("Server responded: ") .
1086 $reason . "</font><br />\n";
1087 error_box($string,$color);
1088 exit;
1089 }
1090 }
1091 }
1092
1093 function sqimap_get_user_server ($imap_server, $username) {
1094 if (substr($imap_server, 0, 4) != "map:") {
1095 return $imap_server;
1096 }
1097 $function = substr($imap_server, 4);
1098 return $function($username);
1099 }
1100
1101 /**
1102 * This is an example that gets imapservers from yellowpages (NIS).
1103 * you can simple put map:map_yp_alias in your $imap_server_address
1104 * in config.php use your own function instead map_yp_alias to map your
1105 * LDAP whatever way to find the users imapserver.
1106 */
1107 function map_yp_alias($username) {
1108 $yp = `ypmatch $username aliases`;
1109 return chop(substr($yp, strlen($username)+1));
1110 }
1111
1112 ?>