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