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