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