adding param and since tags to some functions
[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 return $imap_stream;
822 }
823
824 /**
825 * Simply logs out the IMAP session
826 * @param stream imap_stream the IMAP connection to log out.
827 * @return void
828 */
829 function sqimap_logout ($imap_stream) {
830 /* Logout is not valid until the server returns 'BYE'
831 * If we don't have an imap_ stream we're already logged out */
832 if(isset($imap_stream) && $imap_stream)
833 sqimap_run_command($imap_stream, 'LOGOUT', false, $response, $message);
834 }
835
836 /**
837 * Retreive the CAPABILITY string from the IMAP server.
838 * If capability is set, returns only that specific capability,
839 * else returns array of all capabilities.
840 * @param $imap_stream
841 * @param string $capability (optional since 1.3.0)
842 * @return mixed (string if $capability is set and found,
843 * false, if $capability is set and not found,
844 * array if $capability not set)
845 */
846 function sqimap_capability($imap_stream, $capability='') {
847 global $sqimap_capabilities;
848 if (!is_array($sqimap_capabilities)) {
849 $read = sqimap_run_command($imap_stream, 'CAPABILITY', true, $a, $b);
850
851 $c = explode(' ', $read[0]);
852 for ($i=2; $i < count($c); $i++) {
853 $cap_list = explode('=', $c[$i]);
854 if (isset($cap_list[1])) {
855 // FIX ME. capabilities can occure multiple times.
856 // THREAD=REFERENCES THREAD=ORDEREDSUBJECT
857 $sqimap_capabilities[$cap_list[0]] = $cap_list[1];
858 } else {
859 $sqimap_capabilities[$cap_list[0]] = TRUE;
860 }
861 }
862 }
863 if ($capability) {
864 if (isset($sqimap_capabilities[$capability])) {
865 return $sqimap_capabilities[$capability];
866 } else {
867 return false;
868 }
869 }
870 return $sqimap_capabilities;
871 }
872
873 /**
874 * Returns the delimeter between mailboxes: INBOX/Test, or INBOX.Test
875 * @param stream $imap_stream
876 * @return string
877 */
878 function sqimap_get_delimiter ($imap_stream = false) {
879 global $sqimap_delimiter, $optional_delimiter;
880
881 /* Use configured delimiter if set */
882 if((!empty($optional_delimiter)) && $optional_delimiter != 'detect') {
883 return $optional_delimiter;
884 }
885
886 /* Do some caching here */
887 if (!$sqimap_delimiter) {
888 if (sqimap_capability($imap_stream, 'NAMESPACE')) {
889 /*
890 * According to something that I can't find, this is supposed to work on all systems
891 * OS: This won't work in Courier IMAP.
892 * OS: According to rfc2342 response from NAMESPACE command is:
893 * OS: * NAMESPACE (PERSONAL NAMESPACES) (OTHER_USERS NAMESPACE) (SHARED NAMESPACES)
894 * OS: We want to lookup all personal NAMESPACES...
895 */
896 $read = sqimap_run_command($imap_stream, 'NAMESPACE', true, $a, $b);
897 if (eregi('\\* NAMESPACE +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL)', $read[0], $data)) {
898 if (eregi('^\\( *\\((.*)\\) *\\)', $data[1], $data2)) {
899 $pn = $data2[1];
900 }
901 $pna = explode(')(', $pn);
902 while (list($k, $v) = each($pna)) {
903 $lst = explode('"', $v);
904 if (isset($lst[3])) {
905 $pn[$lst[1]] = $lst[3];
906 } else {
907 $pn[$lst[1]] = '';
908 }
909 }
910 }
911 $sqimap_delimiter = $pn[0];
912 } else {
913 fputs ($imap_stream, ". LIST \"INBOX\" \"\"\r\n");
914 $read = sqimap_read_data($imap_stream, '.', true, $a, $b);
915 $read = $read['.'][0]; //sqimap_read_data() now returns a tag array of response array
916 $quote_position = strpos ($read[0], '"');
917 $sqimap_delimiter = substr ($read[0], $quote_position+1, 1);
918 }
919 }
920 return $sqimap_delimiter;
921 }
922
923 /**
924 * This encodes a mailbox name for use in IMAP commands.
925 * @param string what the mailbox to encode
926 * @return string the encoded mailbox string
927 * @since 1.5.0
928 */
929 function sqimap_encode_mailbox_name($what)
930 {
931 if (ereg("[\"\\\r\n]", $what))
932 return '{' . strlen($what) . "}\r\n" . $what; /* 4.3 literal form */
933 return '"' . $what . '"'; /* 4.3 quoted string form */
934 }
935
936 /**
937 * Gets the number of messages in the current mailbox.
938 *
939 * OBSOLETE use sqimap_status_messages instead.
940 */
941 function sqimap_get_num_messages ($imap_stream, $mailbox) {
942 $read_ary = sqimap_run_command ($imap_stream, 'EXAMINE ' . sqimap_encode_mailbox_name($mailbox), false, $result, $message);
943 for ($i = 0; $i < count($read_ary); $i++) {
944 if (ereg("[^ ]+ +([^ ]+) +EXISTS", $read_ary[$i], $regs)) {
945 return $regs[1];
946 }
947 }
948 return false; //"BUG! Couldn't get number of messages in $mailbox!";
949 }
950 include_once(SM_PATH . 'functions/rfc822address.php');
951
952 /**
953 * OBSOLETE FUNCTION should be removed after mailbox_display,
954 * printMessage function is adapted
955 */
956 function parseAddress($address, $max=0) {
957 $aAddress = parseRFC822Address($address,array('limit'=> $max));
958 /*
959 * Because the expected format of the array element is changed we adapt it now.
960 * This also implies that this function is obsolete and should be removed after the
961 * rest of the source is adapted. See Rfc822Address.php for the new function.
962 */
963 array_walk($aAddress, '_adaptAddress');
964 return $aAddress;
965 }
966
967 /**
968 * OBSOLETE FUNCTION should be removed after mailbox_display,
969 * printMessage function is adapted
970 */
971 function _adaptAddress(&$aAddr,$k) {
972 $sPersonal = (isset($aAddr[SQM_ADDR_PERSONAL]) && $aAddr[SQM_ADDR_PERSONAL]) ?
973 $aAddr[SQM_ADDR_PERSONAL] : '';
974 $sEmail = ($aAddr[SQM_ADDR_HOST]) ?
975 $aAddr[SQM_ADDR_MAILBOX] . '@'.$aAddr[SQM_ADDR_HOST] :
976 $aAddr[SQM_ADDR_MAILBOX];
977 $aAddr = array($sEmail,$sPersonal);
978 }
979
980 /**
981 * Returns the number of unseen messages in this folder.
982 * obsoleted by sqimap_status_messages !
983 */
984 function sqimap_unseen_messages ($imap_stream, $mailbox) {
985 $aStatus = sqimap_status_messages($imap_stream,$mailbox,array('UNSEEN'));
986 return $aStatus['UNSEEN'];
987 }
988
989 /**
990 * Returns the status items of a mailbox.
991 * Default it returns MESSAGES,UNSEEN and RECENT
992 * Supported status items are MESSAGES, UNSEEN, RECENT, UIDNEXT and UIDVALIDITY
993 */
994 function sqimap_status_messages ($imap_stream, $mailbox,
995 $aStatusItems = array('MESSAGES','UNSEEN','RECENT')) {
996
997 $aStatusItems = implode(' ',$aStatusItems);
998 $read_ary = sqimap_run_command ($imap_stream, 'STATUS ' . sqimap_encode_mailbox_name($mailbox) .
999 " ($aStatusItems)", false, $result, $message);
1000 $i = 0;
1001 $messages = $unseen = $recent = $uidnext = $uidvalidity = false;
1002 $regs = array(false,false);
1003 while (isset($read_ary[$i])) {
1004 if (preg_match('/UNSEEN\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1005 $unseen = $regs[1];
1006 }
1007 if (preg_match('/MESSAGES\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1008 $messages = $regs[1];
1009 }
1010 if (preg_match('/RECENT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1011 $recent = $regs[1];
1012 }
1013 if (preg_match('/UIDNEXT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1014 $uidnext = $regs[1];
1015 }
1016 if (preg_match('/UIDVALIDITY\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1017 $uidvalidity = $regs[1];
1018 }
1019 $i++;
1020 }
1021 return array('MESSAGES' => $messages,
1022 'UNSEEN'=>$unseen,
1023 'RECENT' => $recent,
1024 'UIDNEXT' => $uidnext,
1025 'UIDVALIDITY' => $uidvalidity);
1026 }
1027
1028
1029 /**
1030 * Saves a message to a given folder -- used for saving sent messages
1031 */
1032 function sqimap_append ($imap_stream, $sent_folder, $length) {
1033 fputs ($imap_stream, sqimap_session_id() . ' APPEND ' . sqimap_encode_mailbox_name($sent_folder) . " (\\Seen) \{$length}\r\n");
1034 $tmp = fgets ($imap_stream, 1024);
1035 sqimap_append_checkresponse($tmp, $sent_folder);
1036 }
1037
1038 function sqimap_append_done ($imap_stream, $folder='') {
1039 fputs ($imap_stream, "\r\n");
1040 $tmp = fgets ($imap_stream, 1024);
1041 sqimap_append_checkresponse($tmp, $folder);
1042 }
1043
1044 function sqimap_append_checkresponse($response, $folder) {
1045
1046 if (preg_match("/(.*)(BAD|NO)(.*)$/", $response, $regs)) {
1047 global $squirrelmail_language, $color;
1048 set_up_language($squirrelmail_language);
1049 require_once(SM_PATH . 'functions/display_messages.php');
1050
1051 $reason = $regs[3];
1052 if ($regs[2] == 'NO') {
1053 $string = "<b><font color=\"$color[2]\">\n" .
1054 _("ERROR : Could not append message to") ." $folder." .
1055 "</b><br />\n" .
1056 _("Server responded: ") .
1057 $reason . "<br />\n";
1058 if (preg_match("/(.*)(quota)(.*)$/i", $reason, $regs)) {
1059 $string .= _("Solution: ") .
1060 _("Remove unneccessary messages from your folder and start with your Trash folder.")
1061 ."<br />\n";
1062 }
1063 $string .= "</font>\n";
1064 error_box($string,$color);
1065 } else {
1066 $string = "<b><font color=\"$color[2]\">\n" .
1067 _("ERROR : Bad or malformed request.") .
1068 "</b><br />\n" .
1069 _("Server responded: ") .
1070 $reason . "</font><br />\n";
1071 error_box($string,$color);
1072 exit;
1073 }
1074 }
1075 }
1076
1077 function sqimap_get_user_server ($imap_server, $username) {
1078 if (substr($imap_server, 0, 4) != "map:") {
1079 return $imap_server;
1080 }
1081 $function = substr($imap_server, 4);
1082 return $function($username);
1083 }
1084
1085 /**
1086 * This is an example that gets imapservers from yellowpages (NIS).
1087 * you can simple put map:map_yp_alias in your $imap_server_address
1088 * in config.php use your own function instead map_yp_alias to map your
1089 * LDAP whatever way to find the users imapserver.
1090 */
1091 function map_yp_alias($username) {
1092 $yp = `ypmatch $username aliases`;
1093 return chop(substr($yp, strlen($username)+1));
1094 }
1095
1096 ?>