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