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