6 * This implements all functions that do general IMAP functions.
8 * @copyright © 1999-2007 The SquirrelMail Project Team
9 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
11 * @package squirrelmail
17 require_once(SM_PATH
. 'functions/rfc822address.php');
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'.
29 function sqimap_session_id($unique_id = FALSE) {
30 static $sqimap_session_id = 1;
33 return( sprintf("A%03d", $sqimap_session_id++
) );
35 return( sprintf("A%03d", $sqimap_session_id++
) . ' UID' );
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.
52 function sqimap_run_command_list ($imap_stream, $query, $handle_errors, &$response, &$message, $unique_id = false) {
54 $sid = sqimap_session_id($unique_id);
55 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
56 $tag_uid_a = explode(' ',trim($sid));
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];
63 //FIXME: obey $handle_errors below!
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!
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.
91 function sqimap_run_command ($imap_stream, $query, $handle_errors, &$response,
92 &$message, $unique_id = false,$filter=false,
93 $outputstream=false,$no_return=false) {
95 $sid = sqimap_session_id($unique_id);
96 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
97 $tag_uid_a = explode(' ',trim($sid));
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
107 /* retrieve the response and the message */
108 $response = $response[$tag];
109 $message = $message[$tag];
111 if (!empty($read[$tag])) {
112 return $read[$tag][0];
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!
130 * @param mixed $new_query
132 * @param array $aQuery
133 * @param boolean $unique_id see sqimap_session_id()
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;
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()
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) {
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.
168 After testing it on Exchange 2000 we discovered that a chunksize of 32
169 was quicker then when we raised it to 128.
171 $iQueryCount = count($aQueryList);
173 // array_chunk would also do the job but it's supported from php > 4.2
174 $aQueryChunks = array();
175 $iLoops = floor($iQueryCount / $iChunkSize);
177 if ($iLoops * $iChunkSize != $iQueryCount) ++
$iLoops;
179 if (!function_exists('array_chunk')) { // arraychunk replacement
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;
187 $aQueryChunks[] = $aTmp;
190 $aQueryChunks = array_chunk($aQueryList,$iChunkSize,true);
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;
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];
208 $aResults[$returned_tag] = $aResponse;
210 $aServerResponse[$returned_tag] = $response[$returned_tag];
211 $aServerMessage[$returned_tag] = $message[$returned_tag];
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
226 function sqimap_fgets($imap_stream) {
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 */
238 if ( $results != '' ) {
239 $offset = strlen($results) - 1;
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
255 function sqimap_fread($imap_stream,$iSize,$filter=false,
256 $outputstream=false, $no_return=false) {
257 if (!$filter ||
!$outputstream) {
258 $iBufferSize = $iSize;
260 // see php bug 24033. They changed fread behaviour %$^&$%
261 $iBufferSize = 7800; // multiple of 78 in case of base64 decoding.
263 if ($iSize < $iBufferSize) {
264 $iBufferSize = $iSize;
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;
283 if ($sReadRem != '') {
284 $sRead = $sReadRem . $sRead;
288 if ($filter && $sRead != '') {
289 // in case the filter is base64 decoding we return a remainder
290 $sReadRem = $filter($sRead);
292 if ($outputstream && $sRead != '') {
293 if (is_resource($outputstream)) {
294 fwrite($outputstream,$sRead);
295 } else if ($outputstream == 'php://stdout') {
310 * Obsolete function, inform plugins that use it
311 * @param stream $imap_stream
313 * @param boolean $handle_errors
314 * @param array $response
315 * @param array $message
316 * @param string $query
318 * @deprecated (since 1.5.0) use sqimap_run_command or sqimap_run_command_list instead
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!
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";
338 $oTemplate->display('footer.tpl');
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
352 function sqimap_error_box($title, $query = '', $message_title = '', $message = '', $link = '')
354 global $color, $squirrelmail_language;
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]);
362 if ($query != '' && $cmd != 'login')
363 $string .= _("Query:") . ' ' . htmlspecialchars($query) . '<br />';
364 if ($message_title != '')
365 $string .= $message_title;
367 $string .= htmlspecialchars($message);
368 //FIXME: NO HTML IN CORE!
369 $string .= "</font><br />\n";
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
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()
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;
395 if (!is_array($message)) $message = array();
396 if (!is_array($response)) $response = array();
398 $resultlist = array();
401 if (preg_match("/^(\w+)\s*/",$query,$aMatch)) {
402 $sCommand = strtoupper($aMatch[1]);
404 // error reporting (shouldn't happen)
406 $read = sqimap_fgets($imap_stream);
414 $read = sqimap_fgets($imap_stream);
419 /* get the command */
422 $s = substr($read,$i);
423 if (($j = strpos($s,' ')) ||
($j = strpos($s,"\n"))) {
424 $arg = substr($s,0,$j);
426 $found_tag = substr($read,0,$i-1);
435 $response[$found_tag] = $arg;
436 $message[$found_tag] = trim(substr($read,$i+
strlen($arg)));
438 $resultlist[] = $data;
440 $aResponse[$found_tag] = $resultlist;
441 $data = $resultlist = array();
442 if ($found_tag == $tag) {
443 break 3; /* switch switch while */
447 /* this shouldn't happen */
448 $response[$found_tag] = $arg;
449 $message[$found_tag] = trim(substr($read,$i+
strlen($arg)));
451 $resultlist[] = $data;
453 $aResponse[$found_tag] = $resultlist;
454 $data = $resultlist = array();
455 if ($found_tag == $tag) {
456 break 3; /* switch switch while */
460 $read = sqimap_fgets($imap_stream);
461 if ($read === false) { /* error */
462 break 2; /* switch while */
465 } // end case $tag{0}
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
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 */
488 /* backwards compattibility */
489 $aLiteral = explode("\n", $sLiteral);
490 /* release not neaded data */
492 foreach ($aLiteral as $line) {
493 $fetch_data[] = $line ."\n";
495 /* release not neaded data */
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 */
504 $s = substr($read,-3);
505 $read_literal = true;
508 $fetch_data[] = $read;
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 */
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 */
527 $s = substr($read,-3);
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)) {
537 $sLiteral = fread($imap_stream,$iLit);
538 if ($sLiteral === false) { /* error */
540 break 3; /* while switch while */
543 $data[] = sqimap_fgets($imap_stream);
550 $read = sqimap_fgets($imap_stream);
551 if ($read === false) {
552 break 3; /* while switch while */
553 } else if ($read{0} == '*') {
556 $s = substr($read,-3);
557 } while ($s === "}\r\n");
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) {
573 sqimap_error_box(_("ERROR: IMAP server closed the connection."), $query, _("Server responded:"),$sResponse);
574 //FIXME: NO HTML IN CORE!
575 echo '</body></html>';
577 } else if ($handle_errors) {
579 sqimap_error_box(_("ERROR: Connection dropped by IMAP server."), $query);
584 /* Set $resultlist array */
586 //$resultlist[] = $data;
588 elseif (empty($resultlist)) {
589 $resultlist[] = array();
592 /* Return result or handle errors */
593 if ($handle_errors == false) {
596 switch ($response[$tag]) {
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>';
609 sqimap_error_box(_("ERROR: Bad or malformed request."), $query, _("Server responded:") . ' ', $message[$tag]);
610 //FIXME: NO HTML IN CORE!
611 echo '</body></html>';
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>';
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 */
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()
638 function sqimap_read_data ($imap_stream, $tag_uid, $handle_errors,
639 &$response, &$message, $query = '',
640 $filter=false,$outputstream=false,$no_return=false) {
642 $tag_uid_a = explode(' ',trim($tag_uid));
643 $tag = $tag_uid_a[0];
645 $res = sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
646 $response, $message, $query,$filter,$outputstream,$no_return);
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)
660 function sqimap_create_stream($server,$port,$tls=0) {
661 global $squirrelmail_language;
663 if (strstr($server,':') && ! preg_match("/^\[.*\]$/",$server)) {
664 // numerical IPv6 address must be enclosed in square brackets
665 $server = '['.$server.']';
669 if ((check_php_version(4,3)) and (extension_loaded('openssl'))) {
670 /* Use TLS by prefixing "tls://" to the hostname */
671 $server = 'tls://' . $server;
673 require_once(SM_PATH
. 'functions/display_messages.php');
674 logout_error( sprintf(_("Error connecting to IMAP server: %s."), $server).
676 _("TLS is enabled, but this version of PHP does not support TLS sockets, or is missing the openssl extension.").
678 _("Please contact your system administrator and report this error."),
679 sprintf(_("Error connecting to IMAP server: %s."), $server));
683 $imap_stream = @fsockopen
($server, $port, $error_number, $error_string, 15);
685 /* Do some error correction */
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) );
695 $server_info = fgets ($imap_stream, 1024);
698 * Implementing IMAP STARTTLS (rfc2595) in php 5.1.0+
699 * http://www.php.net/stream-socket-enable-crypto
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),
708 _("IMAP STARTTLS is enabled in SquirrelMail configuration, but used IMAP server does not support STARTTLS."));
712 // issue starttls command and check response
713 sqimap_run_command($imap_stream, 'STARTTLS', false, $starttls_response, $starttls_message);
715 if ($starttls_response!='OK') {
716 // starttls command failed
717 sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
719 _("Server replied:") . ' ',
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
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.
736 //sqsession_unregister('sqimap_capabilities');
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)
744 sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
746 _("Unable to start TLS."));
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.
756 // php install does not support stream_socket_enable_crypto() function
757 sqimap_error_box(sprintf(_("Error connecting to IMAP server: %s."), $server),
759 _("IMAP STARTTLS is enabled in SquirrelMail configuration, but used PHP version does not support functions that allow to enable encryption on open socket."));
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
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:
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
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;
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.
793 sqgetglobalvar('authz' , $authz , SQ_SESSION
);
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;
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.");
813 /* get imap login password */
814 if ($password===false) {
815 /* standard functions */
816 $password = sqauth_read_password();
818 /* old way. $key must be extracted from cookie */
819 if (!isset($onetimepad) ||
empty($onetimepad)) {
820 sqgetglobalvar('onetimepad' , $onetimepad , SQ_SESSION
);
822 /* Decrypt the password */
823 $password = OneTimePadDecrypt($password, $onetimepad);
826 if (!isset($sqimap_capabilities)) {
827 sqgetglobalvar('sqimap_capabilities' , $sqimap_capabilities , SQ_SESSION
);
830 $host = $imap_server_address;
831 $imap_server_address = sqimap_get_user_server($imap_server_address, $username);
833 $imap_stream = sqimap_create_stream($imap_server_address,$imap_port,$use_imap_tls);
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";
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);
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);
864 $results=explode(" ",$read,3);
865 $response=$results[1];
866 $message=$results[2];
868 // Fake the response, so the error trap at the bottom will work
870 $message='IMAP server does not appear to support the authentication method selected.';
871 $message .= ' Please contact your system administrator.';
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') {
879 * SASL PLAIN, RFC 4616 (updates 2595)
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.
891 $tag=sqimap_session_id(false);
892 $sasl = (isset($sqimap_capabilities['SASL-IR']) && $sqimap_capabilities['SASL-IR']) ?
true : false;
894 $auth = base64_encode("$username\0$authz\0$password");
896 $auth = base64_encode("$username\0$username\0$password");
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);
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);
913 $results=explode(" ",$read,3);
914 $response=$results[1];
915 $message=$results[2];
919 $message="Internal SquirrelMail error - unknown IMAP authentication method chosen. Please contact the developers.";
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);
934 if ($hide == 3) return sprintf(_("Unknown error: %s"), $message);
935 $string = sprintf (_("Unknown error: %s") . "<br />\n", $message);
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";
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
953 * $squirrelmail_language is set by a cookie when
954 * the user selects language and logs out
957 set_up_language($squirrelmail_language, true);
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.") );
967 if ($hide == 2) return FALSE;
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.
978 if ( stristr($message, 'REFERRAL imap') === TRUE ) {
979 sqimap_logout($imap_stream);
980 set_up_language($squirrelmail_language, true);
982 logout_error( _("Your mailbox is not located at this server. Try a different server or consult your system administrator") );
990 * Simply logs out the IMAP session
991 * @param stream $imap_stream the IMAP connection to log out.
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);
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)
1012 function sqimap_capability($imap_stream, $capability='', $bUseCache=true) {
1013 // sqgetGlobalVar('sqimap_capabilities', $sqimap_capabilities, SQ_SESSION);
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:
1026 unset($sqimap_capabilities[trim($cap_list[0])]);
1028 $sqimap_capabilities[trim($cap_list[0])][] = $cap_list[1];
1030 if(!isset($sqimap_capabilities[trim($cap_list[0])])) {
1031 $sqimap_capabilities[trim($cap_list[0])] = TRUE;
1037 if (isset($sqimap_capabilities[$capability])) {
1038 return $sqimap_capabilities[$capability];
1043 return $sqimap_capabilities;
1047 * Returns the delimiter between mailboxes: INBOX/Test, or INBOX.Test
1048 * @param stream $imap_stream
1051 function sqimap_get_delimiter ($imap_stream = false) {
1052 global $sqimap_delimiter, $optional_delimiter;
1054 /* Use configured delimiter if set */
1055 if((!empty($optional_delimiter)) && $optional_delimiter != 'detect') {
1056 return $optional_delimiter;
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
);
1064 /* Do some caching here */
1065 if (!$sqimap_delimiter) {
1066 if (sqimap_capability($imap_stream, 'NAMESPACE')) {
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...
1074 * TODO: remove this in favour of the information from sqimap_get_namespace()
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)) {
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];
1091 $sqimap_delimiter = $pn[0];
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);
1100 return $sqimap_delimiter;
1104 * Retrieves the namespaces from the IMAP server.
1105 * NAMESPACE is an IMAP extension defined in RFC 2342.
1107 * @param stream $imap_stream
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]);
1116 * Parses a NAMESPACE response and returns an array with the available
1117 * personal, users and shared namespaces.
1119 * @param string $input
1120 * @return array The returned array has the following format:
1123 * 'personal' => array(
1124 * 0 => array('prefix'=>'INBOX.','delimiter' =>'.'),
1127 * 'users' => array(..
1129 * 'shared' => array( ..
1133 * Note that if a namespace is not defined in the server, then the corresponding
1134 * array will be empty.
1136 function sqimap_parse_namespace(&$input) {
1137 $ns_strings = array(1=>'personal', 2=>'users', 3=>'shared');
1138 $namespace = array();
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();
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;
1155 // $regs[3] is $regs[2] without the quotes
1156 $namespace[$ns_strings[$i]][$j]['delimiter'] = $regs3[3];
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
1174 function sqimap_encode_mailbox_name($what)
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 */
1182 * Gets the number of messages in the current mailbox.
1184 * OBSOLETE use sqimap_status_messages instead.
1185 * @param stream $imap_stream imap stream
1186 * @param string $mailbox
1189 function sqimap_get_num_messages ($imap_stream, $mailbox) {
1190 $aStatus = sqimap_status_messages($imap_stream,$mailbox,array('MESSAGES'));
1191 return $aStatus['MESSAGES'];
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
1201 * @deprecated See Rfc822Address.php
1203 function parseAddress($address, $max=0) {
1204 $aAddress = parseRFC822Address($address,array('limit'=> $max));
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.
1210 array_walk($aAddress, '_adaptAddress');
1215 * OBSOLETE FUNCTION should be removed after mailbox_display,
1216 * printMessage function is adapted
1218 * callback function used for formating of addresses array in
1219 * parseAddress() function
1220 * @param array $aAddr
1221 * @param integer $k array key
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);
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
1243 function sqimap_unseen_messages ($imap_stream, $mailbox) {
1244 $aStatus = sqimap_status_messages($imap_stream,$mailbox,array('UNSEEN'));
1245 return $aStatus['UNSEEN'];
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
1259 function sqimap_status_messages ($imap_stream, $mailbox,
1260 $aStatusItems = array('MESSAGES','UNSEEN','RECENT')) {
1262 $aStatusItems = implode(' ',$aStatusItems);
1263 $read_ary = sqimap_run_command ($imap_stream, 'STATUS ' . sqimap_encode_mailbox_name($mailbox) .
1264 " ($aStatusItems)", false, $result, $message);
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)) {
1272 if (preg_match('/MESSAGES\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1273 $messages = $regs[1];
1275 if (preg_match('/RECENT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1278 if (preg_match('/UIDNEXT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1279 $uidnext = $regs[1];
1281 if (preg_match('/UIDVALIDITY\s+([0-9]+)/i', $read_ary[$i], $regs)) {
1282 $uidvalidity = $regs[1];
1287 $status=array('MESSAGES' => $messages,
1289 'RECENT' => $recent,
1290 'UIDNEXT' => $uidnext,
1291 'UIDVALIDITY' => $uidvalidity);
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);
1306 * Saves a message to a given folder -- used for saving sent messages
1307 * @param stream $imap_stream
1308 * @param string $sent_folder
1310 * @return string $sid
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);
1322 * @param stream imap_stream
1323 * @param string $folder (since 1.3.2)
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);
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
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;
1348 $imapquery = $query;
1353 if ($response{0} == '+') {
1354 // continuation request triggerd by sqimap_append()
1357 $i = strpos($response, ' ');
1358 $sRsp = substr($response,0,$i);
1359 $sMsg = substr($response,$i+
1);
1360 $aExtra = array('MAILBOX' => $sMailbox);
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);
1374 // $imapsid<space>$sRsp<space>$sMsg
1376 $i = strpos($sMsg, ' ');
1377 $sRsp = strtoupper(substr($sMsg,0,$i));
1378 $sMsg = substr($sMsg,$i+
1);
1381 if (preg_match("/(.*)(quota)(.*)$/i", $sMsg, $aMatch)) {
1382 sqm_trigger_imap_error('SQM_IMAP_APPEND_QUOTA_ERROR',$imapquery,$sRsp,$sMsg,$aExtra);
1384 sqm_trigger_imap_error('SQM_IMAP_APPEND_ERROR',$imapquery,$sRsp,$sMsg,$aExtra);
1388 sqm_trigger_imap_error('SQM_IMAP_ERROR',$imapquery,$sRsp,$sMsg,$aExtra);
1391 sqm_trigger_imap_error('SQM_IMAP_BYE',$imapquery,$sRsp,$sMsg,$aExtra);
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
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
1416 function sqimap_get_user_server ($imap_server, $username) {
1417 if (substr($imap_server, 0, 4) != "map:") {
1418 return $imap_server;
1420 $function = substr($imap_server, 4);
1421 return $function($username);
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.
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
1436 function map_yp_alias($username) {
1437 $yp = `ypmatch
$username aliases`
;
1438 return chop(substr($yp, strlen($username)+
1));