Add address list pagination, 'Compose To' button, more labels for checkboxes, hook...
[squirrelmail.git] / functions / imap_general.php
1 <?php
2
3 /**
4 * imap_general.php
5 *
6 * This implements all functions that do general IMAP functions.
7 *
8 * @copyright &copy; 1999-2007 The SquirrelMail Project Team
9 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
10 * @version $Id$
11 * @package squirrelmail
12 * @subpackage imap
13 */
14
15 /** Includes.. */
16
17 require_once(SM_PATH . 'functions/rfc822address.php');
18
19
20 /**
21 * Generates a new session ID by incrementing the last one used;
22 * this ensures that each command has a unique ID.
23 * @param bool $unique_id (since 1.3.0) controls use of unique
24 * identifiers/message sequence numbers in IMAP commands. See IMAP
25 * rfc 'UID command' chapter.
26 * @return string IMAP session id of the form 'A000'.
27 * @since 1.2.0
28 */
29 function sqimap_session_id($unique_id = FALSE) {
30 static $sqimap_session_id = 1;
31
32 if (!$unique_id) {
33 return( sprintf("A%03d", $sqimap_session_id++) );
34 } else {
35 return( sprintf("A%03d", $sqimap_session_id++) . ' UID' );
36 }
37 }
38
39 /**
40 * Both send a command and accept the result from the command.
41 * This is to allow proper session number handling.
42 * @param stream $imap_stream imap connection resource
43 * @param string $query imap command
44 * @param boolean $handle_errors see sqimap_retrieve_imap_response()
45 * @param array $response
46 * @param array $message
47 * @param boolean $unique_id (since 1.3.0) see sqimap_session_id().
48 * @return mixed returns false on imap error. displays error message
49 * if imap stream is not available.
50 * @since 1.2.3
51 */
52 function sqimap_run_command_list ($imap_stream, $query, $handle_errors, &$response, &$message, $unique_id = false) {
53 if ($imap_stream) {
54 $sid = sqimap_session_id($unique_id);
55 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
56 $tag_uid_a = explode(' ',trim($sid));
57 $tag = $tag_uid_a[0];
58 $read = sqimap_retrieve_imap_response ($imap_stream, $tag, $handle_errors, $response, $message, $query );
59 /* get the response and the message */
60 $message = $message[$tag];
61 $response = $response[$tag];
62 return $read[$tag];
63 } else {
64 global $squirrelmail_language, $color;
65 set_up_language($squirrelmail_language);
66 //FIXME: NO HTML IN CORE!
67 $string = "<b><font color=\"$color[2]\">\n" .
68 _("ERROR: No available IMAP stream.") .
69 //FIXME: NO HTML IN CORE!
70 "</b></font>\n";
71 error_box($string);
72 return false;
73 }
74 }
75
76 /**
77 * @param stream $imap_stream imap connection resource
78 * @param string $query imap command
79 * @param boolean $handle_errors see sqimap_retrieve_imap_response()
80 * @param array $response empty string, if return = false
81 * @param array $message empty string, if return = false
82 * @param boolean $unique_id (since 1.3.0) see sqimap_session_id()
83 * @param boolean $filter (since 1.4.1 and 1.5.0) see sqimap_fread()
84 * @param mixed $outputstream (since 1.4.1 and 1.5.0) see sqimap_fread()
85 * @param boolean $no_return (since 1.4.1 and 1.5.0) see sqimap_fread()
86 * @return mixed returns false on imap error. displays error message
87 * if imap stream is not available.
88 * @since 1.2.3
89 */
90 function sqimap_run_command ($imap_stream, $query, $handle_errors, &$response,
91 &$message, $unique_id = false,$filter=false,
92 $outputstream=false,$no_return=false) {
93 if ($imap_stream) {
94 $sid = sqimap_session_id($unique_id);
95 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
96 $tag_uid_a = explode(' ',trim($sid));
97 $tag = $tag_uid_a[0];
98
99 $read = sqimap_read_data ($imap_stream, $tag, $handle_errors, $response,
100 $message, $query,$filter,$outputstream,$no_return);
101 if (empty($read)) { //IMAP server dropped its connection
102 $response = '';
103 $message = '';
104 return false;
105 }
106 /* retrieve the response and the message */
107 $response = $response[$tag];
108 $message = $message[$tag];
109
110 if (!empty($read[$tag])) {
111 return $read[$tag][0];
112 } else {
113 return $read[$tag];
114 }
115 } else {
116 global $squirrelmail_language, $color;
117 set_up_language($squirrelmail_language);
118 //FIXME: NO HTML IN CORE!
119 $string = "<b><font color=\"$color[2]\">\n" .
120 _("ERROR: No available IMAP stream.") .
121 //FIXME: NO HTML IN CORE!
122 "</b></font>\n";
123 error_box($string);
124 return false;
125 }
126 }
127
128 /**
129 * @param mixed $new_query
130 * @param string $tag
131 * @param array $aQuery
132 * @param boolean $unique_id see sqimap_session_id()
133 * @since 1.5.0
134 */
135 function sqimap_prepare_pipelined_query($new_query,&$tag,&$aQuery,$unique_id) {
136 $sid = sqimap_session_id($unique_id);
137 $tag_uid_a = explode(' ',trim($sid));
138 $tag = $tag_uid_a[0];
139 $query = $sid . ' '.$new_query."\r\n";
140 $aQuery[$tag] = $query;
141 }
142
143 /**
144 * @param stream $imap_stream imap stream
145 * @param array $aQueryList
146 * @param boolean $handle_errors
147 * @param array $aServerResponse
148 * @param array $aServerMessage
149 * @param boolean $unique_id see sqimap_session_id()
150 * @param boolean $filter see sqimap_fread()
151 * @param mixed $outputstream see sqimap_fread()
152 * @param boolean $no_return see sqimap_fread()
153 * @since 1.5.0
154 */
155 function sqimap_run_pipelined_command ($imap_stream, $aQueryList, $handle_errors,
156 &$aServerResponse, &$aServerMessage, $unique_id = false,
157 $filter=false,$outputstream=false,$no_return=false) {
158 $aResponse = false;
159
160 /*
161 Do not fire all calls at once to the IMAP server but split the calls up
162 in portions of $iChunkSize. If we do not do that I think we misbehave as
163 IMAP client or should handle BYE calls if the IMAP server drops the
164 connection because the number of queries is to large. This isn't tested
165 but a wild guess how it could work in the field.
166
167 After testing it on Exchange 2000 we discovered that a chunksize of 32
168 was quicker then when we raised it to 128.
169 */
170 $iQueryCount = count($aQueryList);
171 $iChunkSize = 32;
172 // array_chunk would also do the job but it's supported from php > 4.2
173 $aQueryChunks = array();
174 $iLoops = floor($iQueryCount / $iChunkSize);
175
176 if ($iLoops * $iChunkSize != $iQueryCount) ++$iLoops;
177
178 if (!function_exists('array_chunk')) { // arraychunk replacement
179 reset($aQueryList);
180 for($i=0;$i<$iLoops;++$i) {
181 for($j=0;$j<$iChunkSize;++$j) {
182 $key = key($aQueryList);
183 $aTmp[$key] = $aQueryList[$key];
184 if (next($aQueryList) === false) break;
185 }
186 $aQueryChunks[] = $aTmp;
187 }
188 } else {
189 $aQueryChunks = array_chunk($aQueryList,$iChunkSize,true);
190 }
191
192 for ($i=0;$i<$iLoops;++$i) {
193 $aQuery = $aQueryChunks[$i];
194 foreach($aQuery as $tag => $query) {
195 fputs($imap_stream,$query);
196 $aResults[$tag] = false;
197 }
198 foreach($aQuery as $tag => $query) {
199 if ($aResults[$tag] == false) {
200 $aReturnedResponse = sqimap_retrieve_imap_response ($imap_stream, $tag,
201 $handle_errors, $response, $message, $query,
202 $filter,$outputstream,$no_return);
203 foreach ($aReturnedResponse as $returned_tag => $aResponse) {
204 if (!empty($aResponse)) {
205 $aResults[$returned_tag] = $aResponse[0];
206 } else {
207 $aResults[$returned_tag] = $aResponse;
208 }
209 $aServerResponse[$returned_tag] = $response[$returned_tag];
210 $aServerMessage[$returned_tag] = $message[$returned_tag];
211 }
212 }
213 }
214 }
215 return $aResults;
216 }
217
218 /**
219 * Custom fgets function: gets a line from the IMAP server,
220 * no matter how big it may be.
221 * @param stream $imap_stream the stream to read from
222 * @return string a line
223 * @since 1.2.8
224 */
225 function sqimap_fgets($imap_stream) {
226 $read = '';
227 $buffer = 4096;
228 $results = '';
229 $offset = 0;
230 while (strpos($results, "\r\n", $offset) === false) {
231 if (!($read = fgets($imap_stream, $buffer))) {
232 /* this happens in case of an error */
233 /* reset $results because it's useless */
234 $results = false;
235 break;
236 }
237 if ( $results != '' ) {
238 $offset = strlen($results) - 1;
239 }
240 $results .= $read;
241 }
242 return $results;
243 }
244
245 /**
246 * @param stream $imap_stream
247 * @param integer $iSize
248 * @param boolean $filter
249 * @param mixed $outputstream stream or 'php://stdout' string
250 * @param boolean $no_return controls data returned by function
251 * @return string
252 * @since 1.4.1
253 */
254 function sqimap_fread($imap_stream,$iSize,$filter=false,
255 $outputstream=false, $no_return=false) {
256 if (!$filter || !$outputstream) {
257 $iBufferSize = $iSize;
258 } else {
259 // see php bug 24033. They changed fread behaviour %$^&$%
260 $iBufferSize = 7800; // multiple of 78 in case of base64 decoding.
261 }
262 if ($iSize < $iBufferSize) {
263 $iBufferSize = $iSize;
264 }
265
266 $iRetrieved = 0;
267 $results = '';
268 $sRead = $sReadRem = '';
269 // NB: fread can also stop at end of a packet on sockets.
270 while ($iRetrieved < $iSize) {
271 $sRead = fread($imap_stream,$iBufferSize);
272 $iLength = strlen($sRead);
273 $iRetrieved += $iLength ;
274 $iRemaining = $iSize - $iRetrieved;
275 if ($iRemaining < $iBufferSize) {
276 $iBufferSize = $iRemaining;
277 }
278 if ($sRead == '') {
279 $results = false;
280 break;
281 }
282 if ($sReadRem != '') {
283 $sRead = $sReadRem . $sRead;
284 $sReadRem = '';
285 }
286
287 if ($filter && $sRead != '') {
288 // in case the filter is base64 decoding we return a remainder
289 $sReadRem = $filter($sRead);
290 }
291 if ($outputstream && $sRead != '') {
292 if (is_resource($outputstream)) {
293 fwrite($outputstream,$sRead);
294 } else if ($outputstream == 'php://stdout') {
295 echo $sRead;
296 }
297 }
298 if ($no_return) {
299 $sRead = '';
300 } else {
301 $results .= $sRead;
302 }
303 }
304 return $results;
305 }
306
307
308 /**
309 * Obsolete function, inform plugins that use it
310 * @param stream $imap_stream
311 * @param string $tag
312 * @param boolean $handle_errors
313 * @param array $response
314 * @param array $message
315 * @param string $query
316 * @since 1.1.3
317 * @deprecated (since 1.5.0) use sqimap_run_command or sqimap_run_command_list instead
318 */
319 function sqimap_read_data_list($imap_stream, $tag, $handle_errors,
320 &$response, &$message, $query = '') {
321 global $color, $oTemplate, $squirrelmail_language;
322 set_up_language($squirrelmail_language);
323 //FIXME: NO HTML IN CORE!
324 $string = "<b><font color=\"$color[2]\">\n" .
325 _("ERROR: Bad function call.") .
326 //FIXME: NO HTML IN CORE!
327 "</b><br />\n" .
328 _("Reason:") . ' '.
329 'There is a plugin installed which make use of the <br />' .
330 'SquirrelMail internal function sqimap_read_data_list.<br />'.
331 'Please adapt the installed plugin and let it use<br />'.
332 'sqimap_run_command or sqimap_run_command_list instead<br /><br />'.
333 'The following query was issued:<br />'.
334 //FIXME: NO HTML IN CORE!
335 htmlspecialchars($query) . '<br />' . "</font><br />\n";
336 error_box($string);
337 $oTemplate->display('footer.tpl');
338 exit;
339 }
340
341 /**
342 * Function to display an error related to an IMAP query.
343 * @param string title the caption of the error box
344 * @param string query the query that went wrong
345 * @param string message_title optional message title
346 * @param string message optional error message
347 * @param string $link an optional link to try again
348 * @return void
349 * @since 1.5.0
350 */
351 function sqimap_error_box($title, $query = '', $message_title = '', $message = '', $link = '')
352 {
353 global $color, $squirrelmail_language;
354
355 set_up_language($squirrelmail_language);
356 //FIXME: NO HTML IN CORE!
357 $string = "<font color=\"$color[2]\"><b>\n" . $title . "</b><br />\n";
358 $cmd = explode(' ',$query);
359 $cmd= strtolower($cmd[0]);
360
361 if ($query != '' && $cmd != 'login')
362 $string .= _("Query:") . ' ' . htmlspecialchars($query) . '<br />';
363 if ($message_title != '')
364 $string .= $message_title;
365 if ($message != '')
366 $string .= htmlspecialchars($message);
367 //FIXME: NO HTML IN CORE!
368 $string .= "</font><br />\n";
369 if ($link != '')
370 $string .= $link;
371 error_box($string);
372 }
373
374 /**
375 * Reads the output from the IMAP stream. If handle_errors is set to true,
376 * this will also handle all errors that are received. If it is not set,
377 * the errors will be sent back through $response and $message.
378 * @param stream $imap_stream imap stream
379 * @param string $tag
380 * @param boolean $handle_errors handle errors internally or send them in $response and $message.
381 * @param array $response
382 * @param array $message
383 * @param string $query command that can be printed if something fails
384 * @param boolean $filter see sqimap_fread()
385 * @param mixed $outputstream see sqimap_fread()
386 * @param boolean $no_return see sqimap_fread()
387 * @since 1.5.0
388 */
389 function sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
390 &$response, &$message, $query = '',
391 $filter = false, $outputstream = false, $no_return = false) {
392 global $color, $squirrelmail_language;
393 $read = '';
394 if (!is_array($message)) $message = array();
395 if (!is_array($response)) $response = array();
396 $aResponse = '';
397 $resultlist = array();
398 $data = array();
399 $sCommand = '';
400 if (preg_match("/^(\w+)\s*/",$query,$aMatch)) {
401 $sCommand = strtoupper($aMatch[1]);
402 } else {
403 // error reporting (shouldn't happen)
404 }
405 $read = sqimap_fgets($imap_stream);
406 $i = 0;
407 while ($read) {
408 $char = $read{0};
409 switch ($char)
410 {
411 case '+':
412 default:
413 $read = sqimap_fgets($imap_stream);
414 break;
415
416 case $tag{0}:
417 {
418 /* get the command */
419 $arg = '';
420 $i = strlen($tag)+1;
421 $s = substr($read,$i);
422 if (($j = strpos($s,' ')) || ($j = strpos($s,"\n"))) {
423 $arg = substr($s,0,$j);
424 }
425 $found_tag = substr($read,0,$i-1);
426 if ($found_tag) {
427 switch ($arg)
428 {
429 case 'OK':
430 case 'BAD':
431 case 'NO':
432 case 'BYE':
433 case 'PREAUTH':
434 $response[$found_tag] = $arg;
435 $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
436 if (!empty($data)) {
437 $resultlist[] = $data;
438 }
439 $aResponse[$found_tag] = $resultlist;
440 $data = $resultlist = array();
441 if ($found_tag == $tag) {
442 break 3; /* switch switch while */
443 }
444 break;
445 default:
446 /* this shouldn't happen */
447 $response[$found_tag] = $arg;
448 $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
449 if (!empty($data)) {
450 $resultlist[] = $data;
451 }
452 $aResponse[$found_tag] = $resultlist;
453 $data = $resultlist = array();
454 if ($found_tag == $tag) {
455 break 3; /* switch switch while */
456 }
457 }
458 }
459 $read = sqimap_fgets($imap_stream);
460 if ($read === false) { /* error */
461 break 2; /* switch while */
462 }
463 break;
464 } // end case $tag{0}
465
466 case '*':
467 {
468 if (($sCommand == "FETCH" || $sCommand == "STORE") && preg_match('/^\*\s\d+\sFETCH/',$read)) {
469 /* check for literal */
470 $s = substr($read,-3);
471 $fetch_data = array();
472 do { /* outer loop, continue until next untagged fetch
473 or tagged reponse */
474 do { /* innerloop for fetching literals. with this loop
475 we prohibid that literal responses appear in the
476 outer loop so we can trust the untagged and
477 tagged info provided by $read */
478 $read_literal = false;
479 if ($s === "}\r\n") {
480 $j = strrpos($read,'{');
481 $iLit = substr($read,$j+1,-3);
482 $fetch_data[] = $read;
483 $sLiteral = sqimap_fread($imap_stream,$iLit,$filter,$outputstream,$no_return);
484 if ($sLiteral === false) { /* error */
485 break 4; /* while while switch while */
486 }
487 /* backwards compattibility */
488 $aLiteral = explode("\n", $sLiteral);
489 /* release not neaded data */
490 unset($sLiteral);
491 foreach ($aLiteral as $line) {
492 $fetch_data[] = $line ."\n";
493 }
494 /* release not neaded data */
495 unset($aLiteral);
496 /* next fgets belongs to this fetch because
497 we just got the exact literalsize and there
498 must follow data to complete the response */
499 $read = sqimap_fgets($imap_stream);
500 if ($read === false) { /* error */
501 break 4; /* while while switch while */
502 }
503 $s = substr($read,-3);
504 $read_literal = true;
505 continue;
506 } else {
507 $fetch_data[] = $read;
508 }
509 /* retrieve next line and check in the while
510 statements if it belongs to this fetch response */
511 $read = sqimap_fgets($imap_stream);
512 if ($read === false) { /* error */
513 break 4; /* while while switch while */
514 }
515 /* check for next untagged reponse and break */
516 if ($read{0} == '*') break 2;
517 $s = substr($read,-3);
518 } while ($s === "}\r\n" || $read_literal);
519 $s = substr($read,-3);
520 } while ($read{0} !== '*' &&
521 substr($read,0,strlen($tag)) !== $tag);
522 $resultlist[] = $fetch_data;
523 /* release not neaded data */
524 unset ($fetch_data);
525 } else {
526 $s = substr($read,-3);
527 do {
528 if ($s === "}\r\n") {
529 $j = strrpos($read,'{');
530 $iLit = substr($read,$j+1,-3);
531 // check for numeric value to avoid that untagged responses like:
532 // * OK [PARSE] Unexpected characters at end of address: {SET:debug=51}
533 // will trigger literal fetching ({SET:debug=51} !== int )
534 if (is_numeric($iLit)) {
535 $data[] = $read;
536 $sLiteral = fread($imap_stream,$iLit);
537 if ($sLiteral === false) { /* error */
538 $read = false;
539 break 3; /* while switch while */
540 }
541 $data[] = $sLiteral;
542 $data[] = sqimap_fgets($imap_stream);
543 } else {
544 $data[] = $read;
545 }
546 } else {
547 $data[] = $read;
548 }
549 $read = sqimap_fgets($imap_stream);
550 if ($read === false) {
551 break 3; /* while switch while */
552 } else if ($read{0} == '*') {
553 break;
554 }
555 $s = substr($read,-3);
556 } while ($s === "}\r\n");
557 break 1;
558 }
559 break;
560 } // end case '*'
561 } // end switch
562 } // end while
563
564 /* error processing in case $read is false */
565 if ($read === false) {
566 // try to retrieve an untagged bye respons from the results
567 $sResponse = array_pop($data);
568 if ($sResponse !== NULL && strpos($sResponse,'* BYE') !== false) {
569 if (!$handle_errors) {
570 $query = '';
571 }
572 sqimap_error_box(_("ERROR: IMAP server closed the connection."), $query, _("Server responded:"),$sResponse);
573 //FIXME: NO HTML IN CORE!
574 echo '</body></html>';
575 exit;
576 } else if ($handle_errors) {
577 unset($data);
578 sqimap_error_box(_("ERROR: Connection dropped by IMAP server."), $query);
579 exit;
580 }
581 }
582
583 /* Set $resultlist array */
584 if (!empty($data)) {
585 //$resultlist[] = $data;
586 }
587 elseif (empty($resultlist)) {
588 $resultlist[] = array();
589 }
590
591 /* Return result or handle errors */
592 if ($handle_errors == false) {
593 return $aResponse;
594 }
595 switch ($response[$tag]) {
596 case 'OK':
597 return $aResponse;
598 break;
599 case 'NO':
600 /* ignore this error from M$ exchange, it is not fatal (aka bug) */
601 if (strstr($message[$tag], 'command resulted in') === false) {
602 sqimap_error_box(_("ERROR: Could not complete request."), $query, _("Reason Given:") . ' ', $message[$tag]);
603 echo '</body></html>';
604 exit;
605 }
606 break;
607 case 'BAD':
608 sqimap_error_box(_("ERROR: Bad or malformed request."), $query, _("Server responded:") . ' ', $message[$tag]);
609 //FIXME: NO HTML IN CORE!
610 echo '</body></html>';
611 exit;
612 case 'BYE':
613 sqimap_error_box(_("ERROR: IMAP server closed the connection."), $query, _("Server responded:") . ' ', $message[$tag]);
614 //FIXME: NO HTML IN CORE!
615 echo '</body></html>';
616 exit;
617 default:
618 sqimap_error_box(_("ERROR: Unknown IMAP response."), $query, _("Server responded:") . ' ', $message[$tag]);
619 /* the error is displayed but because we don't know the reponse we
620 return the result anyway */
621 return $aResponse;
622 break;
623 }
624 }
625
626 /**
627 * @param stream $imap_stream imap string
628 * @param string $tag_uid
629 * @param boolean $handle_errors
630 * @param array $response
631 * @param array $message
632 * @param string $query (since 1.2.5)
633 * @param boolean $filter (since 1.4.1) see sqimap_fread()
634 * @param mixed $outputstream (since 1.4.1) see sqimap_fread()
635 * @param boolean $no_return (since 1.4.1) see sqimap_fread()
636 */
637 function sqimap_read_data ($imap_stream, $tag_uid, $handle_errors,
638 &$response, &$message, $query = '',
639 $filter=false,$outputstream=false,$no_return=false) {
640
641 $tag_uid_a = explode(' ',trim($tag_uid));
642 $tag = $tag_uid_a[0];
643
644 $res = sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
645 $response, $message, $query,$filter,$outputstream,$no_return);
646 return $res;
647 }
648
649 /**
650 * Connects to the IMAP server and returns a resource identifier for use with
651 * the other SquirrelMail IMAP functions. Does NOT login!
652 * @param string server hostname of IMAP server
653 * @param int port port number to connect to
654 * @param integer $tls whether to use plain text(0), TLS(1) or STARTTLS(2) when connecting.
655 * Argument was boolean before 1.5.1.
656 * @return imap-stream resource identifier
657 * @since 1.5.0 (usable only in 1.5.1 or later)
658 */
659 function sqimap_create_stream($server,$port,$tls=0) {
660 global $squirrelmail_language;
661
662 if (strstr($server,':') && ! preg_match("/^\[.*\]$/",$server)) {
663 // numerical IPv6 address must be enclosed in square brackets
664 $server = '['.$server.']';
665 }
666
667 if ($tls == 1) {
668 if ((check_php_version(4,3)) and (extension_loaded('openssl'))) {
669 /* Use TLS by prefixing "tls://" to the hostname */
670 $server = 'tls://' . $server;
671 } else {
672 require_once(SM_PATH . 'functions/display_messages.php');
673 logout_error( sprintf(_("Error connecting to IMAP server: %s."), $server).
674 '<br />'.
675 _("TLS is enabled, but this version of PHP does not support TLS sockets, or is missing the openssl extension.").
676 '<br /><br />'.
677 _("Please contact your system administrator and report this error."),
678 sprintf(_("Error connecting to IMAP server: %s."), $server));
679 }
680 }
681
682 $imap_stream = @fsockopen($server, $port, $error_number, $error_string, 15);
683
684 /* Do some error correction */
685 if (!$imap_stream) {
686 set_up_language($squirrelmail_language, true);
687 require_once(SM_PATH . 'functions/display_messages.php');
688 logout_error( sprintf(_("Error connecting to IMAP server: %s."), $server).
689 //FIXME: NO HTML IN CORE!
690 "<br />\r\n$error_number : $error_string<br />\r\n",
691 sprintf(_("Error connecting to IMAP server: %s."), $server) );
692 exit;
693 }
694 $server_info = fgets ($imap_stream, 1024);
695
696 /**
697 * Implementing IMAP STARTTLS (rfc2595) in php 5.1.0+
698 * http://www.php.net/stream-socket-enable-crypto
699 */
700 if ($tls === 2) {
701 if (function_exists('stream_socket_enable_crypto')) {
702 // check starttls capability, don't use cached capability version
703 if (! sqimap_capability($imap_stream, 'STARTTLS', false)) {
704 // imap server does not declare starttls support
705 sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
706 '','',
707 _("IMAP STARTTLS is enabled in SquirrelMail configuration, but used IMAP server does not support STARTTLS."));
708 exit;
709 }
710
711 // issue starttls command and check response
712 sqimap_run_command($imap_stream, 'STARTTLS', false, $starttls_response, $starttls_message);
713 // check response
714 if ($starttls_response!='OK') {
715 // starttls command failed
716 sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
717 'STARTTLS',
718 _("Server replied:") . ' ',
719 $starttls_message);
720 exit();
721 }
722
723 // start crypto on connection. suppress function errors.
724 if (@stream_socket_enable_crypto($imap_stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
725 // starttls was successful
726
727 /**
728 * RFC 2595 requires to discard CAPABILITY information after successful
729 * STARTTLS command. We don't follow RFC, because SquirrelMail stores CAPABILITY
730 * information only after successful login (src/redirect.php) and cached information
731 * is used only in other php script connections after successful STARTTLS. If script
732 * issues sqimap_capability() call before sqimap_login() and wants to get initial
733 * capability response, script should set third sqimap_capability() argument to false.
734 */
735 //sqsession_unregister('sqimap_capabilities');
736 } else {
737 /**
738 * stream_socket_enable_crypto() call failed. Possible issues:
739 * - broken ssl certificate (uw drops connection, error is in syslog mail facility)
740 * - some ssl error (can reproduce with STREAM_CRYPTO_METHOD_SSLv3_CLIENT, PHP E_WARNING
741 * suppressed in stream_socket_enable_crypto() call)
742 */
743 sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
744 '','',
745 _("Unable to start TLS."));
746 /**
747 * Bug: stream_socket_enable_crypto() does not register SSL errors in
748 * openssl_error_string() or stream notification wrapper and displays
749 * them in E_WARNING level message. It is impossible to retrieve error
750 * message without own error handler.
751 */
752 exit;
753 }
754 } else {
755 // php install does not support stream_socket_enable_crypto() function
756 sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
757 '','',
758 _("IMAP STARTTLS is enabled in SquirrelMail configuration, but used PHP version does not support functions that allow to enable encryption on open socket."));
759 exit;
760 }
761 }
762 return $imap_stream;
763 }
764
765 /**
766 * Logs the user into the IMAP server. If $hide is set, no error messages
767 * will be displayed (if set to 1, just exits, if set to 2, returns FALSE).
768 * This function returns the IMAP connection handle.
769 * @param string $username user name
770 * @param string $password password encrypted with onetimepad. Since 1.5.2
771 * function can use internal password functions, if parameter is set to
772 * boolean false.
773 * @param string $imap_server_address address of imap server
774 * @param integer $imap_port port of imap server
775 * @param int $hide controls display connection errors:
776 * 0 = do not hide
777 * 1 = show no errors (just exit)
778 * 2 = show no errors (return FALSE)
779 * 3 = show no errors (return error string)
780 * @return mixed The IMAP connection stream, or if the connection fails,
781 * FALSE if $hide is set to 2 or an error string if $hide
782 * is set to 3.
783 */
784 function sqimap_login ($username, $password, $imap_server_address, $imap_port, $hide) {
785 global $color, $squirrelmail_language, $onetimepad, $use_imap_tls,
786 $imap_auth_mech, $sqimap_capabilities;
787
788 // Note/TODO: This hack grabs the $authz argument from the session. In the short future,
789 // a new argument in function sqimap_login() will be used instead.
790 $authz = '';
791 global $authz;
792 sqgetglobalvar('authz' , $authz , SQ_SESSION);
793
794 if(!empty($authz)) {
795 /* authz plugin - specific:
796 * Get proxy login parameters from authz plugin configuration. If they
797 * exist, they will override the current ones.
798 * This is useful if we want to use different SASL authentication mechanism
799 * and/or different TLS settings for proxy logins. */
800 global $authz_imap_auth_mech, $authz_use_imap_tls, $authz_imapPort_tls;
801 $imap_auth_mech = !empty($authz_imap_auth_mech) ? strtolower($authz_imap_auth_mech) : $imap_auth_mech;
802 $use_imap_tls = !empty($authz_use_imap_tls)? $authz_use_imap_tls : $use_imap_tls;
803 $imap_port = !empty($authz_use_imap_tls)? $authz_imapPort_tls : $imap_port;
804
805 if($imap_auth_mech == 'login' || $imap_auth_mech == 'cram-md5') {
806 logout_error("Misconfigured Plugin (authz or equivalent):<br/>".
807 "The LOGIN and CRAM-MD5 authentication mechanisms cannot be used when attempting proxy login.");
808 exit;
809 }
810 }
811
812 /* get imap login password */
813 if ($password===false) {
814 /* standard functions */
815 $password = sqauth_read_password();
816 } else {
817 /* old way. $key must be extracted from cookie */
818 if (!isset($onetimepad) || empty($onetimepad)) {
819 sqgetglobalvar('onetimepad' , $onetimepad , SQ_SESSION );
820 }
821 /* Decrypt the password */
822 $password = OneTimePadDecrypt($password, $onetimepad);
823 }
824
825 if (!isset($sqimap_capabilities)) {
826 sqgetglobalvar('sqimap_capabilities' , $sqimap_capabilities , SQ_SESSION );
827 }
828
829 $host = $imap_server_address;
830 $imap_server_address = sqimap_get_user_server($imap_server_address, $username);
831
832 $imap_stream = sqimap_create_stream($imap_server_address,$imap_port,$use_imap_tls);
833
834 if (($imap_auth_mech == 'cram-md5') OR ($imap_auth_mech == 'digest-md5')) {
835 // We're using some sort of authentication OTHER than plain or login
836 $tag=sqimap_session_id(false);
837 if ($imap_auth_mech == 'digest-md5') {
838 $query = $tag . " AUTHENTICATE DIGEST-MD5\r\n";
839 } elseif ($imap_auth_mech == 'cram-md5') {
840 $query = $tag . " AUTHENTICATE CRAM-MD5\r\n";
841 }
842 fputs($imap_stream,$query);
843 $answer=sqimap_fgets($imap_stream);
844 // Trim the "+ " off the front
845 $response=explode(" ",$answer,3);
846 if ($response[0] == '+') {
847 // Got a challenge back
848 $challenge=$response[1];
849 if ($imap_auth_mech == 'digest-md5') {
850 $reply = digest_md5_response($username,$password,$challenge,'imap',$host,$authz);
851 } elseif ($imap_auth_mech == 'cram-md5') {
852 $reply = cram_md5_response($username,$password,$challenge);
853 }
854 fputs($imap_stream,$reply);
855 $read=sqimap_fgets($imap_stream);
856 if ($imap_auth_mech == 'digest-md5') {
857 // DIGEST-MD5 has an extra step..
858 if (substr($read,0,1) == '+') { // OK so far..
859 fputs($imap_stream,"\r\n");
860 $read=sqimap_fgets($imap_stream);
861 }
862 }
863 $results=explode(" ",$read,3);
864 $response=$results[1];
865 $message=$results[2];
866 } else {
867 // Fake the response, so the error trap at the bottom will work
868 $response="BAD";
869 $message='IMAP server does not appear to support the authentication method selected.';
870 $message .= ' Please contact your system administrator.';
871 }
872 } elseif ($imap_auth_mech == 'login') {
873 // Original IMAP login code
874 $query = 'LOGIN "' . quoteimap($username) . '" "' . quoteimap($password) . '"';
875 $read = sqimap_run_command ($imap_stream, $query, false, $response, $message);
876 } elseif ($imap_auth_mech == 'plain') {
877 /***
878 * SASL PLAIN, RFC 4616 (updates 2595)
879 *
880 * The mechanism consists of a single message, a string of [UTF-8]
881 * encoded [Unicode] characters, from the client to the server. The
882 * client presents the authorization identity (identity to act as),
883 * followed by a NUL (U+0000) character, followed by the authentication
884 * identity (identity whose password will be used), followed by a NUL
885 * (U+0000) character, followed by the clear-text password. As with
886 * other SASL mechanisms, the client does not provide an authorization
887 * identity when it wishes the server to derive an identity from the
888 * credentials and use that as the authorization identity.
889 */
890 $tag=sqimap_session_id(false);
891 $sasl = (isset($sqimap_capabilities['SASL-IR']) && $sqimap_capabilities['SASL-IR']) ? true : false;
892 if(!empty($authz)) {
893 $auth = base64_encode("$username\0$authz\0$password");
894 } else {
895 $auth = base64_encode("$username\0$username\0$password");
896 }
897 if ($sasl) {
898 // IMAP Extension for SASL Initial Client Response
899 // <draft-siemborski-imap-sasl-initial-response-01b.txt>
900 $query = $tag . " AUTHENTICATE PLAIN $auth\r\n";
901 fputs($imap_stream, $query);
902 $read = sqimap_fgets($imap_stream);
903 } else {
904 $query = $tag . " AUTHENTICATE PLAIN\r\n";
905 fputs($imap_stream, $query);
906 $read=sqimap_fgets($imap_stream);
907 if (substr($read,0,1) == '+') { // OK so far..
908 fputs($imap_stream, "$auth\r\n");
909 $read = sqimap_fgets($imap_stream);
910 }
911 }
912 $results=explode(" ",$read,3);
913 $response=$results[1];
914 $message=$results[2];
915
916 } else {
917 $response="BAD";
918 $message="Internal SquirrelMail error - unknown IMAP authentication method chosen. Please contact the developers.";
919 }
920
921 /* If the connection was not successful, lets see why */
922 if ($response != 'OK') {
923 if (!$hide || $hide == 3) {
924 //FIXME: UUURG... We don't want HTML in error messages, should also do html sanitizing of error messages elsewhere; should't assume output is destined for an HTML browser here
925 if ($response != 'NO') {
926 /* "BAD" and anything else gets reported here. */
927 $message = htmlspecialchars($message);
928 set_up_language($squirrelmail_language, true);
929 if ($response == 'BAD') {
930 if ($hide == 3) return sprintf(_("Bad request: %s"), $message);
931 $string = sprintf (_("Bad request: %s")."<br />\r\n", $message);
932 } else {
933 if ($hide == 3) return sprintf(_("Unknown error: %s"), $message);
934 $string = sprintf (_("Unknown error: %s") . "<br />\n", $message);
935 }
936 if (isset($read) && is_array($read)) {
937 $string .= '<br />' . _("Read data:") . "<br />\n";
938 foreach ($read as $line) {
939 $string .= htmlspecialchars($line) . "<br />\n";
940 }
941 }
942 error_box($string);
943 exit;
944 } else {
945 /*
946 * If the user does not log in with the correct
947 * username and password it is not possible to get the
948 * correct locale from the user's preferences.
949 * Therefore, apply the same hack as on the login
950 * screen.
951 *
952 * $squirrelmail_language is set by a cookie when
953 * the user selects language and logs out
954 */
955
956 set_up_language($squirrelmail_language, true);
957 sqsession_destroy();
958
959 /* terminate the session nicely */
960 sqimap_logout($imap_stream);
961 if ($hide == 3) return _("Unknown user or password incorrect.");
962 logout_error( _("Unknown user or password incorrect.") );
963 exit;
964 }
965 } else {
966 if ($hide == 2) return FALSE;
967 exit;
968 }
969 }
970
971 /* Special error case:
972 * Login referrals. The server returns:
973 * ? OK [REFERRAL <imap url>]
974 * Check RFC 2221 for details. Since we do not support login referrals yet
975 * we log the user out.
976 */
977 if ( stristr($message, 'REFERRAL imap') === TRUE ) {
978 sqimap_logout($imap_stream);
979 set_up_language($squirrelmail_language, true);
980 sqsession_destroy();
981 logout_error( _("Your mailbox is not located at this server. Try a different server or consult your system administrator") );
982 exit;
983 }
984
985 return $imap_stream;
986 }
987
988 /**
989 * Simply logs out the IMAP session
990 * @param stream $imap_stream the IMAP connection to log out.
991 * @return void
992 */
993 function sqimap_logout ($imap_stream) {
994 /* Logout is not valid until the server returns 'BYE'
995 * If we don't have an imap_ stream we're already logged out */
996 if(isset($imap_stream) && $imap_stream)
997 sqimap_run_command($imap_stream, 'LOGOUT', false, $response, $message);
998 }
999
1000 /**
1001 * Retrieve the CAPABILITY string from the IMAP server.
1002 * If capability is set, returns only that specific capability,
1003 * else returns array of all capabilities.
1004 * @param stream $imap_stream
1005 * @param string $capability (since 1.3.0)
1006 * @param boolean $bUseCache (since 1.5.1) Controls use of capability data stored in session
1007 * @return mixed (string if $capability is set and found,
1008 * false, if $capability is set and not found,
1009 * array if $capability not set)
1010 */
1011 function sqimap_capability($imap_stream, $capability='', $bUseCache=true) {
1012 // sqgetGlobalVar('sqimap_capabilities', $sqimap_capabilities, SQ_SESSION);
1013
1014 if (!$bUseCache || ! sqgetGlobalVar('sqimap_capabilities', $sqimap_capabilities, SQ_SESSION)) {
1015 $read = sqimap_run_command($imap_stream, 'CAPABILITY', true, $a, $b);
1016 $c = explode(' ', $read[0]);
1017 for ($i=2; $i < count($c); $i++) {
1018 $cap_list = explode('=', $c[$i]);
1019 if (isset($cap_list[1])) {
1020 if(isset($sqimap_capabilities[trim($cap_list[0])]) &&
1021 !is_array($sqimap_capabilities[trim($cap_list[0])])) {
1022 // Remove array key that was added in 'else' block below
1023 // This is to accomodate for capabilities like:
1024 // SORT SORT=MODSEQ
1025 unset($sqimap_capabilities[trim($cap_list[0])]);
1026 }
1027 $sqimap_capabilities[trim($cap_list[0])][] = $cap_list[1];
1028 } else {
1029 if(!isset($sqimap_capabilities[trim($cap_list[0])])) {
1030 $sqimap_capabilities[trim($cap_list[0])] = TRUE;
1031 }
1032 }
1033 }
1034 }
1035 if ($capability) {
1036 if (isset($sqimap_capabilities[$capability])) {
1037 return $sqimap_capabilities[$capability];
1038 } else {
1039 return false;
1040 }
1041 }
1042 return $sqimap_capabilities;
1043 }
1044
1045 /**
1046 * Returns the delimiter between mailboxes: INBOX/Test, or INBOX.Test
1047 * @param stream $imap_stream
1048 * @return string
1049 */
1050 function sqimap_get_delimiter ($imap_stream = false) {
1051 global $sqimap_delimiter, $optional_delimiter;
1052
1053 /* Use configured delimiter if set */
1054 if((!empty($optional_delimiter)) && $optional_delimiter != 'detect') {
1055 return $optional_delimiter;
1056 }
1057
1058 /* Delimiter is stored in the session from redirect. Try fetching from there first */
1059 if (empty($sqimap_delimiter)) {
1060 sqgetGlobalVar('delimiter',$sqimap_delimiter,SQ_SESSION);
1061 }
1062
1063 /* Do some caching here */
1064 if (!$sqimap_delimiter) {
1065 if (sqimap_capability($imap_stream, 'NAMESPACE')) {
1066 /*
1067 * According to something that I can't find, this is supposed to work on all systems
1068 * OS: This won't work in Courier IMAP.
1069 * OS: According to rfc2342 response from NAMESPACE command is:
1070 * OS: * NAMESPACE (PERSONAL NAMESPACES) (OTHER_USERS NAMESPACE) (SHARED NAMESPACES)
1071 * OS: We want to lookup all personal NAMESPACES...
1072 *
1073 * TODO: remove this in favour of the information from sqimap_get_namespace()
1074 */
1075 $read = sqimap_run_command($imap_stream, 'NAMESPACE', true, $a, $b);
1076 if (eregi('\\* NAMESPACE +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL)', $read[0], $data)) {
1077 if (eregi('^\\( *\\((.*)\\) *\\)', $data[1], $data2)) {
1078 $pn = $data2[1];
1079 }
1080 $pna = explode(')(', $pn);
1081 while (list($k, $v) = each($pna)) {
1082 $lst = explode('"', $v);
1083 if (isset($lst[3])) {
1084 $pn[$lst[1]] = $lst[3];
1085 } else {
1086 $pn[$lst[1]] = '';
1087 }
1088 }
1089 }
1090 $sqimap_delimiter = $pn[0];
1091 } else {
1092 fputs ($imap_stream, ". LIST \"INBOX\" \"\"\r\n");
1093 $read = sqimap_read_data($imap_stream, '.', true, $a, $b);
1094 $read = $read['.'][0]; //sqimap_read_data() now returns a tag array of response array
1095 $quote_position = strpos ($read[0], '"');
1096 $sqimap_delimiter = substr ($read[0], $quote_position+1, 1);
1097 }
1098 }
1099 return $sqimap_delimiter;
1100 }
1101
1102 /**
1103 * Retrieves the namespaces from the IMAP server.
1104 * NAMESPACE is an IMAP extension defined in RFC 2342.
1105 *
1106 * @param stream $imap_stream
1107 * @return array
1108 */
1109 function sqimap_get_namespace($imap_stream) {
1110 $read = sqimap_run_command($imap_stream, 'NAMESPACE', true, $a, $b);
1111 return sqimap_parse_namespace($read[0]);
1112 }
1113
1114 /**
1115 * Parses a NAMESPACE response and returns an array with the available
1116 * personal, users and shared namespaces.
1117 *
1118 * @param string $input
1119 * @return array The returned array has the following format:
1120 * <pre>
1121 * array(
1122 * 'personal' => array(
1123 * 0 => array('prefix'=>'INBOX.','delimiter' =>'.'),
1124 * 1 => ...
1125 * ),
1126 * 'users' => array(..
1127 * ),
1128 * 'shared' => array( ..
1129 * )
1130 * )
1131 * </pre>
1132 * Note that if a namespace is not defined in the server, then the corresponding
1133 * array will be empty.
1134 */
1135 function sqimap_parse_namespace(&$input) {
1136 $ns_strings = array(1=>'personal', 2=>'users', 3=>'shared');
1137 $namespace = array();
1138
1139 if(ereg('NAMESPACE (\(\(.*\)\)|NIL) (\(\(.*\)\)|NIL) (\(\(.*\)\)|NIL)', $input, $regs) !== false) {
1140 for($i=1; $i<=3; $i++) {
1141 if($regs[$i] == 'NIL') {
1142 $namespace[$ns_strings[$i]] = array();
1143 } else {
1144 // Pop-out the first ( and last ) for easier parsing
1145 $ns = substr($regs[$i], 1, sizeof($regs[$i])-2);
1146 if($c = preg_match_all('/\((?:(.*?)\s*?)\)/', $ns, $regs2)) {
1147 $namespace[$ns_strings[$i]] = array();
1148 for($j=0; $j<sizeof($regs2[1]); $j++) {
1149 preg_match('/"(.*)"\s+("(.*)"|NIL)/', $regs2[1][$j], $regs3);
1150 $namespace[$ns_strings[$i]][$j]['prefix'] = $regs3[1];
1151 if($regs3[2] == 'NIL') {
1152 $namespace[$ns_strings[$i]][$j]['delimiter'] = null;
1153 } else {
1154 // $regs[3] is $regs[2] without the quotes
1155 $namespace[$ns_strings[$i]][$j]['delimiter'] = $regs3[3];
1156 }
1157 unset($regs3);
1158 }
1159 }
1160 unset($ns);
1161 }
1162 }
1163 }
1164 return($namespace);
1165 }
1166
1167 /**
1168 * This encodes a mailbox name for use in IMAP commands.
1169 * @param string $what the mailbox to encode
1170 * @return string the encoded mailbox string
1171 * @since 1.5.0
1172 */
1173 function sqimap_encode_mailbox_name($what)
1174 {
1175 if (ereg("[\"\\\r\n]", $what))
1176 return '{' . strlen($what) . "}\r\n" . $what; /* 4.3 literal form */
1177 return '"' . $what . '"'; /* 4.3 quoted string form */
1178 }
1179
1180 /**
1181 * Gets the number of messages in the current mailbox.
1182 *
1183 * OBSOLETE use sqimap_status_messages instead.
1184 * @param stream $imap_stream imap stream
1185 * @param string $mailbox
1186 * @deprecated
1187 */
1188 function sqimap_get_num_messages ($imap_stream, $mailbox) {
1189 $aStatus = sqimap_status_messages($imap_stream,$mailbox,array('MESSAGES'));
1190 return $aStatus['MESSAGES'];
1191 }
1192
1193 /**
1194 * OBSOLETE FUNCTION should be removed after mailbox_display,
1195 * printMessage function is adapted
1196 * $addr_ar = array(), $group = '' and $host='' arguments are used in 1.4.0
1197 * @param string $address
1198 * @param integer $max
1199 * @since 1.4.0
1200 * @deprecated See Rfc822Address.php
1201 */
1202 function parseAddress($address, $max=0) {
1203 $aAddress = parseRFC822Address($address,array('limit'=> $max));
1204 /*
1205 * Because the expected format of the array element is changed we adapt it now.
1206 * This also implies that this function is obsolete and should be removed after the
1207 * rest of the source is adapted. See Rfc822Address.php for the new function.
1208 */
1209 array_walk($aAddress, '_adaptAddress');
1210 return $aAddress;
1211 }
1212
1213 /**
1214 * OBSOLETE FUNCTION should be removed after mailbox_display,
1215 * printMessage function is adapted
1216 *
1217 * callback function used for formating of addresses array in
1218 * parseAddress() function
1219 * @param array $aAddr
1220 * @param integer $k array key
1221 * @since 1.5.1
1222 * @deprecated
1223 */
1224 function _adaptAddress(&$aAddr,$k) {
1225 $sPersonal = (isset($aAddr[SQM_ADDR_PERSONAL]) && $aAddr[SQM_ADDR_PERSONAL]) ?
1226 $aAddr[SQM_ADDR_PERSONAL] : '';
1227 $sEmail = ($aAddr[SQM_ADDR_HOST]) ?
1228 $aAddr[SQM_ADDR_MAILBOX] . '@'.$aAddr[SQM_ADDR_HOST] :
1229 $aAddr[SQM_ADDR_MAILBOX];
1230 $aAddr = array($sEmail,$sPersonal);
1231 }
1232
1233 /**
1234 * Returns the number of unseen messages in this folder.
1235 * obsoleted by sqimap_status_messages !
1236 * Arguments differ in 1.0.x
1237 * @param stream $imap_stream
1238 * @param string $mailbox
1239 * @return integer
1240 * @deprecated
1241 */
1242 function sqimap_unseen_messages ($imap_stream, $mailbox) {
1243 $aStatus = sqimap_status_messages($imap_stream,$mailbox,array('UNSEEN'));
1244 return $aStatus['UNSEEN'];
1245 }
1246
1247 /**
1248 * Returns the status items of a mailbox.
1249 * Default it returns MESSAGES,UNSEEN and RECENT
1250 * Supported status items are MESSAGES, UNSEEN, RECENT (since 1.4.0),
1251 * UIDNEXT (since 1.5.1) and UIDVALIDITY (since 1.5.1)
1252 * @param stream $imap_stream imap stream
1253 * @param string $mailbox mail folder
1254 * @param array $aStatusItems status items
1255 * @return array
1256 * @since 1.3.2
1257 */
1258 function sqimap_status_messages ($imap_stream, $mailbox,
1259 $aStatusItems = array('MESSAGES','UNSEEN','RECENT')) {
1260
1261 $aStatusItems = implode(' ',$aStatusItems);
1262 $read_ary = sqimap_run_command ($imap_stream, 'STATUS ' . sqimap_encode_mailbox_name($mailbox) .
1263 " ($aStatusItems)", false, $result, $message);
1264 $i = 0;
1265 $messages = $unseen = $recent = $uidnext = $uidvalidity = false;
1266 $regs = array(false,false);
1267 while (isset($read_ary[$i])) {
1268 if (preg_match('/UNSEEN\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1269 $unseen = $regs[1];
1270 }
1271 if (preg_match('/MESSAGES\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1272 $messages = $regs[1];
1273 }
1274 if (preg_match('/RECENT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1275 $recent = $regs[1];
1276 }
1277 if (preg_match('/UIDNEXT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1278 $uidnext = $regs[1];
1279 }
1280 if (preg_match('/UIDVALIDITY\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1281 $uidvalidity = $regs[1];
1282 }
1283 $i++;
1284 }
1285
1286 $status=array('MESSAGES' => $messages,
1287 'UNSEEN'=>$unseen,
1288 'RECENT' => $recent,
1289 'UIDNEXT' => $uidnext,
1290 'UIDVALIDITY' => $uidvalidity);
1291
1292 if (!empty($messages)) { $hook_status['MESSAGES']=$messages; }
1293 if (!empty($unseen)) { $hook_status['UNSEEN']=$unseen; }
1294 if (!empty($recent)) { $hook_status['RECENT']=$recent; }
1295 if (!empty($hook_status)) {
1296 $hook_status['MAILBOX']=$mailbox;
1297 $hook_status['CALLER']='sqimap_status_messages';
1298 do_hook('folder_status', $hook_status);
1299 }
1300 return $status;
1301 }
1302
1303
1304 /**
1305 * Saves a message to a given folder -- used for saving sent messages
1306 * @param stream $imap_stream
1307 * @param string $sent_folder
1308 * @param $length
1309 * @return string $sid
1310 */
1311 function sqimap_append ($imap_stream, $sMailbox, $length) {
1312 $sid = sqimap_session_id();
1313 $query = $sid . ' APPEND ' . sqimap_encode_mailbox_name($sMailbox) . " (\\Seen) {".$length."}";
1314 fputs ($imap_stream, "$query\r\n");
1315 $tmp = fgets ($imap_stream, 1024);
1316 sqimap_append_checkresponse($tmp, $sMailbox,$sid, $query);
1317 return $sid;
1318 }
1319
1320 /**
1321 * @param stream imap_stream
1322 * @param string $folder (since 1.3.2)
1323 */
1324 function sqimap_append_done ($imap_stream, $sMailbox='') {
1325 fputs ($imap_stream, "\r\n");
1326 $tmp = fgets ($imap_stream, 1024);
1327 while (!sqimap_append_checkresponse($tmp, $sMailbox)) {
1328 $tmp = fgets ($imap_stream, 1024);
1329 }
1330 }
1331
1332 /**
1333 * Displays error messages, if there are errors in responses to
1334 * commands issues by sqimap_append() and sqimap_append_done() functions.
1335 * @param string $response
1336 * @param string $sMailbox
1337 * @return bool $bDone
1338 * @since 1.5.1 and 1.4.5
1339 */
1340 function sqimap_append_checkresponse($response, $sMailbox, $sid='', $query='') {
1341 // static vars to keep them available when sqimap_append_done calls this function.
1342 static $imapquery, $imapsid;
1343
1344 $bDone = false;
1345
1346 if ($query) {
1347 $imapquery = $query;
1348 }
1349 if ($sid) {
1350 $imapsid = $sid;
1351 }
1352 if ($response{0} == '+') {
1353 // continuation request triggerd by sqimap_append()
1354 $bDone = true;
1355 } else {
1356 $i = strpos($response, ' ');
1357 $sRsp = substr($response,0,$i);
1358 $sMsg = substr($response,$i+1);
1359 $aExtra = array('MAILBOX' => $sMailbox);
1360 switch ($sRsp) {
1361 case '*': //untagged response
1362 $i = strpos($sMsg, ' ');
1363 $sRsp = strtoupper(substr($sMsg,0,$i));
1364 $sMsg = substr($sMsg,$i+1);
1365 if ($sRsp == 'NO' || $sRsp == 'BAD') {
1366 // for the moment disabled. Enable after 1.5.1 release.
1367 // Notices could give valueable information about the mailbox
1368 // sqm_trigger_imap_error('SQM_IMAP_APPEND_NOTICE',$imapquery,$sRsp,$sMsg);
1369 }
1370 $bDone = false;
1371 case $imapsid:
1372 // A001 OK message
1373 // $imapsid<space>$sRsp<space>$sMsg
1374 $bDone = true;
1375 $i = strpos($sMsg, ' ');
1376 $sRsp = strtoupper(substr($sMsg,0,$i));
1377 $sMsg = substr($sMsg,$i+1);
1378 switch ($sRsp) {
1379 case 'NO':
1380 if (preg_match("/(.*)(quota)(.*)$/i", $sMsg, $aMatch)) {
1381 sqm_trigger_imap_error('SQM_IMAP_APPEND_QUOTA_ERROR',$imapquery,$sRsp,$sMsg,$aExtra);
1382 } else {
1383 sqm_trigger_imap_error('SQM_IMAP_APPEND_ERROR',$imapquery,$sRsp,$sMsg,$aExtra);
1384 }
1385 break;
1386 case 'BAD':
1387 sqm_trigger_imap_error('SQM_IMAP_ERROR',$imapquery,$sRsp,$sMsg,$aExtra);
1388 break;
1389 case 'BYE':
1390 sqm_trigger_imap_error('SQM_IMAP_BYE',$imapquery,$sRsp,$sMsg,$aExtra);
1391 break;
1392 case 'OK':
1393 break;
1394 default:
1395 break;
1396 }
1397 break;
1398 default:
1399 // should be false because of the unexpected response but i'm not sure if
1400 // that will cause an endless loop in sqimap_append_done
1401 $bDone = true;
1402 }
1403 }
1404 return $bDone;
1405 }
1406
1407 /**
1408 * Allows mapping of IMAP server address with custom function
1409 * see map_yp_alias()
1410 * @param string $imap_server imap server address or mapping
1411 * @param string $username
1412 * @return string
1413 * @since 1.3.0
1414 */
1415 function sqimap_get_user_server ($imap_server, $username) {
1416 if (substr($imap_server, 0, 4) != "map:") {
1417 return $imap_server;
1418 }
1419 $function = substr($imap_server, 4);
1420 return $function($username);
1421 }
1422
1423 /**
1424 * This is an example that gets IMAP servers from yellowpages (NIS).
1425 * you can simple put map:map_yp_alias in your $imap_server_address
1426 * in config.php use your own function instead map_yp_alias to map your
1427 * LDAP whatever way to find the users IMAP server.
1428 *
1429 * Requires access to external ypmatch program
1430 * FIXME: it can be implemented in php yp extension or pecl (since php 5.1.0)
1431 * @param string $username
1432 * @return string
1433 * @since 1.3.0
1434 */
1435 function map_yp_alias($username) {
1436 $yp = `ypmatch $username aliases`;
1437 return chop(substr($yp, strlen($username)+1));
1438 }