add robots tag here for completeness' sake
[squirrelmail.git] / functions / imap_general.php
CommitLineData
59177427 1<?php
bccadd02 2
35586184 3/**
a6fd80f5 4 * imap_general.php
35586184 5 *
6c84ba1e 6 * Copyright (c) 1999-2005 The SquirrelMail Project Team
15e6162e 7 * Licensed under the GNU GPL. For full terms see the file COPYING.
35586184 8 *
15e6162e 9 * This implements all functions that do general imap functions.
35586184 10 *
eb19bc67 11 * @version $Id$
d6c32258 12 * @package squirrelmail
eb19bc67 13 * @subpackage imap
35586184 14 */
15
d6c32258 16/** Includes.. */
b68edc75 17require_once(SM_PATH . 'functions/page_header.php');
47a29326 18require_once(SM_PATH . 'functions/auth.php');
19
35586184 20
48af4b64 21/**
22 * Generates a new session ID by incrementing the last one used;
23 * this ensures that each command has a unique ID.
b35a5862 24 * @param bool $unique_id (since 1.3.0) controls use of unique
25 * identifiers/message sequence numbers in IMAP commands. See IMAP
26 * rfc 'UID command' chapter.
48af4b64 27 * @return string IMAP session id of the form 'A000'.
b35a5862 28 * @since 1.2.0
48af4b64 29 */
2aca19a1 30function sqimap_session_id($unique_id = FALSE) {
31 static $sqimap_session_id = 1;
32
487daa81 33 if (!$unique_id) {
098ea084 34 return( sprintf("A%03d", $sqimap_session_id++) );
487daa81 35 } else {
098ea084 36 return( sprintf("A%03d", $sqimap_session_id++) . ' UID' );
487daa81 37 }
9c737111 38}
39
48af4b64 40/**
3411d4ec 41 * Both send a command and accept the result from the command.
42 * This is to allow proper session number handling.
b35a5862 43 * @param resource $imap_stream imap connection resource
44 * @param string $query imap command
45 * @param boolean $handle_errors see sqimap_retrieve_imap_response()
46 * @param mixed $response
47 * @param mixed $message
48 * @param boolean $unique_id (since 1.3.0) controls use of unique
49 * identifiers/message sequence numbers in IMAP commands. See IMAP
50 * rfc 'UID command' chapter.
51 * @return mixed returns false on imap error. displays error message
52 * if imap stream is not available.
53 * @since 1.2.3
3411d4ec 54 */
487daa81 55function sqimap_run_command_list ($imap_stream, $query, $handle_errors, &$response, &$message, $unique_id = false) {
c5809184 56 if ($imap_stream) {
098ea084 57 $sid = sqimap_session_id($unique_id);
58 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
bc78cc6e 59 $tag_uid_a = explode(' ',trim($sid));
60 $tag = $tag_uid_a[0];
0dc05a81 61 $read = sqimap_retrieve_imap_response ($imap_stream, $tag, $handle_errors, $response, $message, $query );
bc78cc6e 62 /* get the response and the message */
63 $message = $message[$tag];
1e0a2f0e 64 $response = $response[$tag];
bc78cc6e 65 return $read[$tag];
c5809184 66 } else {
67 global $squirrelmail_language, $color;
68 set_up_language($squirrelmail_language);
69 require_once(SM_PATH . 'functions/display_messages.php');
6fd95361 70 $string = "<b><font color=\"$color[2]\">\n" .
c5809184 71 _("ERROR : No available imapstream.") .
72 "</b></font>\n";
73 error_box($string,$color);
098ea084 74 return false;
c5809184 75 }
1c72b151 76}
77
b35a5862 78/**
79 * @param resource $imap_stream imap connection resource
80 * @param string $query imap command
81 * @param boolean $handle_errors see sqimap_retrieve_imap_response()
82 * @param mixed $response
83 * @param mixed $message
84 * @param boolean $unique_id (since 1.3.0) controls use of unique
85 * identifiers/message sequence numbers in IMAP commands. See IMAP
86 * rfc 'UID command' chapter.
87 * @param mixed $filter (since 1.4.1 and 1.5.0)
88 * @param mixed $outputstream (since 1.4.1 and 1.5.0)
89 * @param mixed $no_return (since 1.4.1 and 1.5.0)
90 * @return mixed returns false on imap error. displays error message
91 * if imap stream is not available.
92 * @since 1.2.3
93 */
1e0a2f0e 94function sqimap_run_command ($imap_stream, $query, $handle_errors, &$response,
7c7b74b3 95 &$message, $unique_id = false,$filter=false,
96 $outputstream=false,$no_return=false) {
c5809184 97 if ($imap_stream) {
98 $sid = sqimap_session_id($unique_id);
1e0a2f0e 99 fputs ($imap_stream, $sid . ' ' . $query . "\r\n");
bc78cc6e 100 $tag_uid_a = explode(' ',trim($sid));
101 $tag = $tag_uid_a[0];
1e0a2f0e 102
bc78cc6e 103 $read = sqimap_read_data ($imap_stream, $tag, $handle_errors, $response,
7c7b74b3 104 $message, $query,$filter,$outputstream,$no_return);
91e0dccc 105 if (empty($read)) { //Imap server dropped its connection
ec73f243 106 $response = '';
107 $message = '';
108 return false;
109 }
bc78cc6e 110 /* retrieve the response and the message */
111 $response = $response[$tag];
112 $message = $message[$tag];
1e0a2f0e 113
bc78cc6e 114 if (!empty($read[$tag])) {
115 return $read[$tag][0];
116 } else {
117 return $read[$tag];
118 }
c5809184 119 } else {
120 global $squirrelmail_language, $color;
121 set_up_language($squirrelmail_language);
122 require_once(SM_PATH . 'functions/display_messages.php');
6fd95361 123 $string = "<b><font color=\"$color[2]\">\n" .
c5809184 124 _("ERROR : No available imapstream.") .
125 "</b></font>\n";
126 error_box($string,$color);
098ea084 127 return false;
1e0a2f0e 128 }
bc78cc6e 129}
48af4b64 130
bc78cc6e 131function sqimap_prepare_pipelined_query($new_query,&$tag,&$aQuery,$unique_id) {
132 $sid = sqimap_session_id($unique_id);
133 $tag_uid_a = explode(' ',trim($sid));
134 $tag = $tag_uid_a[0];
0b38d197 135 $query = $sid . ' '.$new_query."\r\n";
bc78cc6e 136 $aQuery[$tag] = $query;
42a07ac1 137}
138
1e0a2f0e 139function sqimap_run_pipelined_command ($imap_stream, $aQueryList, $handle_errors,
bc78cc6e 140 &$aServerResponse, &$aServerMessage, $unique_id = false,
1e0a2f0e 141 $filter=false,$outputstream=false,$no_return=false) {
bc78cc6e 142 $aResponse = false;
5c300c60 143
1e0a2f0e 144 /*
52c8b585 145 Do not fire all calls at once to the imap-server but split the calls up
1e0a2f0e 146 in portions of $iChunkSize. If we do not do that I think we misbehave as
52c8b585 147 IMAP client or should handle BYE calls if the IMAP-server drops the
148 connection because the number of queries is to large. This isn't tested
149 but a wild guess how it could work in the field.
1e0a2f0e 150
151 After testing it on Exchange 2000 we discovered that a chunksize of 32
0b38d197 152 was quicker then when we raised it to 128.
52c8b585 153 */
154 $iQueryCount = count($aQueryList);
155 $iChunkSize = 32;
156 // array_chunk would also do the job but it's supported from php > 4.2
157 $aQueryChunks = array();
158 $iLoops = floor($iQueryCount / $iChunkSize);
159
5c300c60 160 if ($iLoops * $iChunkSize != $iQueryCount) ++$iLoops;
52c8b585 161
162 if (!function_exists('array_chunk')) { // arraychunk replacement
5c300c60 163 reset($aQueryList);
52c8b585 164 for($i=0;$i<$iLoops;++$i) {
5c300c60 165 for($j=0;$j<$iChunkSize;++$j) {
166 $key = key($aQueryList);
167 $aTmp[$key] = $aQueryList[$key];
168 if (next($aQueryList) === false) break;
169 }
170 $aQueryChunks[] = $aTmp;
1e0a2f0e 171 }
52c8b585 172 } else {
173 $aQueryChunks = array_chunk($aQueryList,$iChunkSize,true);
bc78cc6e 174 }
1e0a2f0e 175
52c8b585 176 for ($i=0;$i<$iLoops;++$i) {
177 $aQuery = $aQueryChunks[$i];
178 foreach($aQuery as $tag => $query) {
179 fputs($imap_stream,$query);
180 $aResults[$tag] = false;
181 }
52c8b585 182 foreach($aQuery as $tag => $query) {
5c300c60 183 if ($aResults[$tag] == false) {
1e0a2f0e 184 $aReturnedResponse = sqimap_retrieve_imap_response ($imap_stream, $tag,
bc78cc6e 185 $handle_errors, $response, $message, $query,
186 $filter,$outputstream,$no_return);
52c8b585 187 foreach ($aReturnedResponse as $returned_tag => $aResponse) {
5c300c60 188 if (!empty($aResponse)) {
52c8b585 189 $aResults[$returned_tag] = $aResponse[0];
5c300c60 190 } else {
191 $aResults[$returned_tag] = $aResponse;
192 }
52c8b585 193 $aServerResponse[$returned_tag] = $response[$returned_tag];
194 $aServerMessage[$returned_tag] = $message[$returned_tag];
195 }
bc78cc6e 196 }
197 }
198 }
b7277e4f 199 return $aResults;
bc78cc6e 200}
9c737111 201
1e0a2f0e 202/**
48af4b64 203 * Custom fgets function: gets a line from the IMAP-server,
204 * no matter how big it may be.
205 * @param stream imap_stream the stream to read from
206 * @return string a line
b8c285ab 207 */
b8c285ab 208function sqimap_fgets($imap_stream) {
209 $read = '';
210 $buffer = 4096;
211 $results = '';
c41daf03 212 $offset = 0;
213 while (strpos($results, "\r\n", $offset) === false) {
b8c285ab 214 if (!($read = fgets($imap_stream, $buffer))) {
329a7ca5 215 /* this happens in case of an error */
216 /* reset $results because it's useless */
217 $results = false;
b8c285ab 218 break;
219 }
c41daf03 220 if ( $results != '' ) {
221 $offset = strlen($results) - 1;
222 }
b8c285ab 223 $results .= $read;
224 }
225 return $results;
226}
227
7c7b74b3 228function sqimap_fread($imap_stream,$iSize,$filter=false,
229 $outputstream=false, $no_return=false) {
230 if (!$filter || !$outputstream) {
231 $iBufferSize = $iSize;
232 } else {
811318c5 233 // see php bug 24033. They changed fread behaviour %$^&$%
977a6085 234 $iBufferSize = 7800; // multiple of 78 in case of base64 decoding.
235 }
236 if ($iSize < $iBufferSize) {
237 $iBufferSize = $iSize;
7c7b74b3 238 }
7c0ec1d8 239
5eba0be2 240 $iRetrieved = 0;
977a6085 241 $results = '';
242 $sRead = $sReadRem = '';
1e0a2f0e 243 // NB: fread can also stop at end of a packet on sockets.
977a6085 244 while ($iRetrieved < $iSize) {
7c7b74b3 245 $sRead = fread($imap_stream,$iBufferSize);
977a6085 246 $iLength = strlen($sRead);
247 $iRetrieved += $iLength ;
248 $iRemaining = $iSize - $iRetrieved;
249 if ($iRemaining < $iBufferSize) {
250 $iBufferSize = $iRemaining;
251 }
13aabbcf 252 if ($sRead == '') {
7c7b74b3 253 $results = false;
254 break;
255 }
13aabbcf 256 if ($sReadRem != '') {
977a6085 257 $sRead = $sReadRem . $sRead;
258 $sReadRem = '';
259 }
7c0ec1d8 260
13aabbcf 261 if ($filter && $sRead != '') {
1e0a2f0e 262 // in case the filter is base64 decoding we return a remainder
7c0ec1d8 263 $sReadRem = $filter($sRead);
7c7b74b3 264 }
13aabbcf 265 if ($outputstream && $sRead != '') {
7c7b74b3 266 if (is_resource($outputstream)) {
267 fwrite($outputstream,$sRead);
268 } else if ($outputstream == 'php://stdout') {
269 echo $sRead;
270 }
271 }
272 if ($no_return) {
273 $sRead = '';
1e0a2f0e 274 } else {
977a6085 275 $results .= $sRead;
7c7b74b3 276 }
7c7b74b3 277 }
1e0a2f0e 278 return $results;
279}
977a6085 280
7c0ec1d8 281
48af4b64 282/**
283 * Obsolete function, inform plugins that use it
b35a5862 284 * @since 1.1.3
285 * @deprecated (since 1.5.0) use sqimap_run_command or sqimap_run_command_list instead
48af4b64 286 */
1e0a2f0e 287function sqimap_read_data_list($imap_stream, $tag, $handle_errors,
0dc05a81 288 &$response, &$message, $query = '') {
289 global $color, $squirrelmail_language;
290 set_up_language($squirrelmail_language);
291 require_once(SM_PATH . 'functions/display_messages.php');
6fd95361 292 $string = "<b><font color=\"$color[2]\">\n" .
0dc05a81 293 _("ERROR : Bad function call.") .
6fd95361 294 "</b><br />\n" .
0dc05a81 295 _("Reason:") . ' '.
6fd95361 296 'There is a plugin installed which make use of the <br />' .
297 'SquirrelMail internal function sqimap_read_data_list.<br />'.
298 'Please adapt the installed plugin and let it use<br />'.
299 'sqimap_run_command or sqimap_run_command_list instead<br /><br />'.
300 'The following query was issued:<br />'.
301 htmlspecialchars($query) . '<br />' . "</font><br />\n";
0dc05a81 302 error_box($string,$color);
1e0a2f0e 303 echo '</body></html>';
304 exit;
0dc05a81 305}
7c7b74b3 306
48af4b64 307/**
308 * Function to display an error related to an IMAP-query.
309 * @param string title the caption of the error box
310 * @param string query the query that went wrong
ec73f243 311 * @param string message_title optional message title
312 * @param string message optional error message
313 * @param string $link an optional link to try again
48af4b64 314 * @return void
315 */
ec73f243 316function sqimap_error_box($title, $query = '', $message_title = '', $message = '', $link = '')
4974c2a0 317{
318 global $color, $squirrelmail_language;
319
320 set_up_language($squirrelmail_language);
321 require_once(SM_PATH . 'functions/display_messages.php');
6fd95361 322 $string = "<font color=\"$color[2]\"><b>\n" . $title . "</b><br />\n";
bf15f116 323 $cmd = explode(' ',$query);
324 $cmd= strtolower($cmd[0]);
325
326 if ($query != '' && $cmd != 'login')
6fd95361 327 $string .= _("Query:") . ' ' . htmlspecialchars($query) . '<br />';
4974c2a0 328 if ($message_title != '')
329 $string .= $message_title;
330 if ($message != '')
331 $string .= htmlspecialchars($message);
6fd95361 332 $string .= "</font><br />\n";
ec73f243 333 if ($link != '')
334 $string .= $link;
4974c2a0 335 error_box($string,$color);
336}
337
48af4b64 338/**
3411d4ec 339 * Reads the output from the IMAP stream. If handle_errors is set to true,
340 * this will also handle all errors that are received. If it is not set,
48af4b64 341 * the errors will be sent back through $response and $message.
bee165ef 342 */
1e0a2f0e 343function sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
7c7b74b3 344 &$response, &$message, $query = '',
345 $filter = false, $outputstream = false, $no_return = false) {
9c737111 346 global $color, $squirrelmail_language;
9c737111 347 $read = '';
bc78cc6e 348 if (!is_array($message)) $message = array();
349 if (!is_array($response)) $response = array();
ec73f243 350 $aResponse = '';
b8c285ab 351 $resultlist = array();
352 $data = array();
353 $read = sqimap_fgets($imap_stream);
8d8da447 354 $i = 0;
bea3eb1e 355 while ($read) {
356 $char = $read{0};
357 switch ($char)
358 {
15bc7f66 359 case '+':
360 default:
361 $read = sqimap_fgets($imap_stream);
362 break;
363
364 case $tag{0}:
365 {
bea3eb1e 366 /* get the command */
367 $arg = '';
368 $i = strlen($tag)+1;
369 $s = substr($read,$i);
370 if (($j = strpos($s,' ')) || ($j = strpos($s,"\n"))) {
371 $arg = substr($s,0,$j);
372 }
373 $found_tag = substr($read,0,$i-1);
5c300c60 374 if ($found_tag) {
bea3eb1e 375 switch ($arg)
376 {
15bc7f66 377 case 'OK':
378 case 'BAD':
379 case 'NO':
380 case 'BYE':
381 case 'PREAUTH':
bc78cc6e 382 $response[$found_tag] = $arg;
383 $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
384 if (!empty($data)) {
385 $resultlist[] = $data;
386 }
5c300c60 387 $aResponse[$found_tag] = $resultlist;
388 $data = $resultlist = array();
389 if ($found_tag == $tag) {
390 break 3; /* switch switch while */
391 }
392 break;
1e0a2f0e 393 default:
bea3eb1e 394 /* this shouldn't happen */
bc78cc6e 395 $response[$found_tag] = $arg;
396 $message[$found_tag] = trim(substr($read,$i+strlen($arg)));
397 if (!empty($data)) {
398 $resultlist[] = $data;
399 }
400 $aResponse[$found_tag] = $resultlist;
5c300c60 401 $data = $resultlist = array();
402 if ($found_tag == $tag) {
403 break 3; /* switch switch while */
404 }
bc78cc6e 405 }
bea3eb1e 406 }
5c300c60 407 $read = sqimap_fgets($imap_stream);
408 if ($read === false) { /* error */
4920e1e0 409 break 2; /* switch while */
5c300c60 410 }
411 break;
15bc7f66 412 } // end case $tag{0}
413
414 case '*':
415 {
bea3eb1e 416 if (preg_match('/^\*\s\d+\sFETCH/',$read)) {
417 /* check for literal */
418 $s = substr($read,-3);
419 $fetch_data = array();
420 do { /* outer loop, continue until next untagged fetch
421 or tagged reponse */
422 do { /* innerloop for fetching literals. with this loop
423 we prohibid that literal responses appear in the
424 outer loop so we can trust the untagged and
425 tagged info provided by $read */
426 if ($s === "}\r\n") {
427 $j = strrpos($read,'{');
428 $iLit = substr($read,$j+1,-3);
429 $fetch_data[] = $read;
7c7b74b3 430 $sLiteral = sqimap_fread($imap_stream,$iLit,$filter,$outputstream,$no_return);
329a7ca5 431 if ($sLiteral === false) { /* error */
432 break 4; /* while while switch while */
433 }
bea3eb1e 434 /* backwards compattibility */
435 $aLiteral = explode("\n", $sLiteral);
436 /* release not neaded data */
437 unset($sLiteral);
438 foreach ($aLiteral as $line) {
439 $fetch_data[] = $line ."\n";
440 }
441 /* release not neaded data */
1e0a2f0e 442 unset($aLiteral);
bea3eb1e 443 /* next fgets belongs to this fetch because
444 we just got the exact literalsize and there
445 must follow data to complete the response */
329a7ca5 446 $read = sqimap_fgets($imap_stream);
447 if ($read === false) { /* error */
448 break 4; /* while while switch while */
449 }
c0b23345 450 $fetch_data[] = $read;
bea3eb1e 451 } else {
329a7ca5 452 $fetch_data[] = $read;
bea3eb1e 453 }
454 /* retrieve next line and check in the while
455 statements if it belongs to this fetch response */
456 $read = sqimap_fgets($imap_stream);
329a7ca5 457 if ($read === false) { /* error */
458 break 4; /* while while switch while */
459 }
bea3eb1e 460 /* check for next untagged reponse and break */
461 if ($read{0} == '*') break 2;
462 $s = substr($read,-3);
463 } while ($s === "}\r\n");
464 $s = substr($read,-3);
465 } while ($read{0} !== '*' &&
106d4087 466 substr($read,0,strlen($tag)) !== $tag);
329a7ca5 467 $resultlist[] = $fetch_data;
bea3eb1e 468 /* release not neaded data */
469 unset ($fetch_data);
470 } else {
471 $s = substr($read,-3);
472 do {
473 if ($s === "}\r\n") {
474 $j = strrpos($read,'{');
475 $iLit = substr($read,$j+1,-3);
476 $data[] = $read;
329a7ca5 477 $sLiteral = fread($imap_stream,$iLit);
c0b23345 478 if ($sLiteral === false) { /* error */
329a7ca5 479 $read = false;
480 break 3; /* while switch while */
481 }
c0b23345 482 $data[] = $sLiteral;
b7277e4f 483 $data[] = sqimap_fgets($imap_stream);
bea3eb1e 484 } else {
329a7ca5 485 $data[] = $read;
bea3eb1e 486 }
487 $read = sqimap_fgets($imap_stream);
329a7ca5 488 if ($read === false) {
489 break 3; /* while switch while */
490 } else if ($read{0} == '*') {
106d4087 491 break;
492 }
bea3eb1e 493 $s = substr($read,-3);
494 } while ($s === "}\r\n");
495 break 1;
1e0a2f0e 496 }
bea3eb1e 497 break;
15bc7f66 498 } // end case '*'
499 } // end switch
c0b23345 500 } // end while
1e0a2f0e 501
c0b23345 502 /* error processing in case $read is false */
d7542838 503 if ($read === false) {
d3d9ef2d 504 // try to retrieve an untagged bye respons from the results
505 $sResponse = array_pop($data);
d7542838 506 if ($sResponse !== NULL && strpos($sResponse,'* BYE') !== false) {
507 if (!$handle_errors) {
508 $query = '';
509 }
510 sqimap_error_box(_("ERROR : Imap server closed the connection."), $query, _("Server responded:"),$sResponse);
511 echo '</body></html>';
512 exit;
513 } else if ($handle_errors) {
1e0a2f0e 514 unset($data);
ec73f243 515 sqimap_error_box(_("ERROR : Connection dropped by imap-server."), $query);
516 exit;
517 }
b8c285ab 518 }
1e0a2f0e 519
15bc7f66 520 /* Set $resultlist array */
b8c285ab 521 if (!empty($data)) {
bc78cc6e 522 //$resultlist[] = $data;
9c737111 523 }
b8c285ab 524 elseif (empty($resultlist)) {
1e0a2f0e 525 $resultlist[] = array();
b8c285ab 526 }
15bc7f66 527
528 /* Return result or handle errors */
bee165ef 529 if ($handle_errors == false) {
bc78cc6e 530 return $aResponse;
dd381002 531 }
4974c2a0 532 switch ($response[$tag]) {
dd381002 533 case 'OK':
bc78cc6e 534 return $aResponse;
329a7ca5 535 break;
1e0a2f0e 536 case 'NO':
dd381002 537 /* ignore this error from M$ exchange, it is not fatal (aka bug) */
6b6c2e06 538 if (strstr($message[$tag], 'command resulted in') === false) {
4974c2a0 539 sqimap_error_box(_("ERROR : Could not complete request."), $query, _("Reason Given: "), $message[$tag]);
329a7ca5 540 echo '</body></html>';
052e0c26 541 exit;
9c737111 542 }
329a7ca5 543 break;
1e0a2f0e 544 case 'BAD':
4974c2a0 545 sqimap_error_box(_("ERROR : Bad or malformed request."), $query, _("Server responded: "), $message[$tag]);
1e0a2f0e 546 echo '</body></html>';
547 exit;
548 case 'BYE':
4974c2a0 549 sqimap_error_box(_("ERROR : Imap server closed the connection."), $query, _("Server responded: "), $message[$tag]);
1e0a2f0e 550 echo '</body></html>';
9c737111 551 exit;
1e0a2f0e 552 default:
4974c2a0 553 sqimap_error_box(_("ERROR : Unknown imap response."), $query, _("Server responded: "), $message[$tag]);
329a7ca5 554 /* the error is displayed but because we don't know the reponse we
555 return the result anyway */
1e0a2f0e 556 return $aResponse;
329a7ca5 557 break;
9c737111 558 }
9c737111 559}
560
1e0a2f0e 561function sqimap_read_data ($imap_stream, $tag_uid, $handle_errors,
7c7b74b3 562 &$response, &$message, $query = '',
563 $filter=false,$outputstream=false,$no_return=false) {
bc78cc6e 564
565 $tag_uid_a = explode(' ',trim($tag_uid));
566 $tag = $tag_uid_a[0];
567
1e0a2f0e 568 $res = sqimap_retrieve_imap_response($imap_stream, $tag, $handle_errors,
569 $response, $message, $query,$filter,$outputstream,$no_return);
570 return $res;
9c737111 571}
572
48af4b64 573/**
b98732e8 574 * Connects to the IMAP server and returns a resource identifier for use with
575 * the other SquirrelMail IMAP functions. Does NOT login!
48af4b64 576 * @param string server hostname of IMAP server
577 * @param int port port number to connect to
578 * @param bool tls whether to use TLS when connecting.
579 * @return imap-stream resource identifier
b98732e8 580 */
581function sqimap_create_stream($server,$port,$tls=false) {
ce68b76b 582 global $squirrelmail_language;
b98732e8 583
57ffe0af 584 if ($tls == true) {
b98732e8 585 if ((check_php_version(4,3)) and (extension_loaded('openssl'))) {
586 /* Use TLS by prefixing "tls://" to the hostname */
57ffe0af 587 $server = 'tls://' . $server;
b98732e8 588 } else {
589 require_once(SM_PATH . 'functions/display_messages.php');
3b151851 590 logout_error( sprintf(_("Error connecting to IMAP server: %s."), $server).
591 '<br />'.
592 _("TLS is enabled, but this version of PHP does not support TLS sockets, or is missing the openssl extension.").
593 '<br /><br />'.
594 _("Please contact your system administrator and report this error.") );
b98732e8 595 }
596 }
597
82f403aa 598 $imap_stream = @fsockopen($server, $port, $error_number, $error_string, 15);
b98732e8 599
600 /* Do some error correction */
601 if (!$imap_stream) {
602 set_up_language($squirrelmail_language, true);
603 require_once(SM_PATH . 'functions/display_messages.php');
3b151851 604 logout_error( sprintf(_("Error connecting to IMAP server: %s."), $server).
605 "<br />\r\n$error_number : $error_string<br />\r\n" );
b98732e8 606 exit;
607 }
608 $server_info = fgets ($imap_stream, 1024);
609 return $imap_stream;
610}
611
48af4b64 612/**
3411d4ec 613 * Logs the user into the imap server. If $hide is set, no error messages
614 * will be displayed. This function returns the imap connection handle.
615 */
9c737111 616function sqimap_login ($username, $password, $imap_server_address, $imap_port, $hide) {
fe55c7c7 617 global $color, $squirrelmail_language, $onetimepad, $use_imap_tls,
618 $imap_auth_mech, $sqimap_capabilities;
85fc999e 619
eb2f6102 620 if (!isset($onetimepad) || empty($onetimepad)) {
621 sqgetglobalvar('onetimepad' , $onetimepad , SQ_SESSION );
622 }
fe55c7c7 623 if (!isset($sqimap_capabilities)) {
624 sqgetglobalvar('sqimap_capabilities' , $capability , SQ_SESSION );
625 }
626
b98732e8 627 $host = $imap_server_address;
bd9829d7 628 $imap_server_address = sqimap_get_user_server($imap_server_address, $username);
fe55c7c7 629
b98732e8 630 $imap_stream = sqimap_create_stream($imap_server_address,$imap_port,$use_imap_tls);
fe55c7c7 631
2f1f7a12 632 /* Decrypt the password */
633 $password = OneTimePadDecrypt($password, $onetimepad);
634
b98732e8 635 if (($imap_auth_mech == 'cram-md5') OR ($imap_auth_mech == 'digest-md5')) {
3b151851 636 // We're using some sort of authentication OTHER than plain or login
b98732e8 637 $tag=sqimap_session_id(false);
638 if ($imap_auth_mech == 'digest-md5') {
098ea084 639 $query = $tag . " AUTHENTICATE DIGEST-MD5\r\n";
b98732e8 640 } elseif ($imap_auth_mech == 'cram-md5') {
098ea084 641 $query = $tag . " AUTHENTICATE CRAM-MD5\r\n";
b98732e8 642 }
643 fputs($imap_stream,$query);
644 $answer=sqimap_fgets($imap_stream);
645 // Trim the "+ " off the front
646 $response=explode(" ",$answer,3);
647 if ($response[0] == '+') {
098ea084 648 // Got a challenge back
b98732e8 649 $challenge=$response[1];
650 if ($imap_auth_mech == 'digest-md5') {
651 $reply = digest_md5_response($username,$password,$challenge,'imap',$host);
652 } elseif ($imap_auth_mech == 'cram-md5') {
653 $reply = cram_md5_response($username,$password,$challenge);
654 }
655 fputs($imap_stream,$reply);
656 $read=sqimap_fgets($imap_stream);
657 if ($imap_auth_mech == 'digest-md5') {
3b151851 658 // DIGEST-MD5 has an extra step..
b98732e8 659 if (substr($read,0,1) == '+') { // OK so far..
098ea084 660 fputs($imap_stream,"\r\n");
661 $read=sqimap_fgets($imap_stream);
098ea084 662 }
b98732e8 663 }
664 $results=explode(" ",$read,3);
665 $response=$results[1];
666 $message=$results[2];
667 } else {
3b151851 668 // Fake the response, so the error trap at the bottom will work
b98732e8 669 $response="BAD";
670 $message='IMAP server does not appear to support the authentication method selected.';
671 $message .= ' Please contact your system administrator.';
672 }
fe0b18b3 673 } elseif ($imap_auth_mech == 'login') {
b98732e8 674 // Original IMAP login code
675 $query = 'LOGIN "' . quoteimap($username) . '" "' . quoteimap($password) . '"';
676 $read = sqimap_run_command ($imap_stream, $query, false, $response, $message);
1e7fc1cb 677 } elseif ($imap_auth_mech == 'plain') {
fe55c7c7 678 /***
679 * SASL PLAIN
680 *
681 * RFC 2595 Chapter 6
682 *
683 * The mechanism consists of a single message from the client to the
684 * server. The client sends the authorization identity (identity to
685 * login as), followed by a US-ASCII NUL character, followed by the
686 * authentication identity (identity whose password will be used),
687 * followed by a US-ASCII NUL character, followed by the clear-text
688 * password. The client may leave the authorization identity empty to
689 * indicate that it is the same as the authentication identity.
690 *
691 **/
b98732e8 692 $tag=sqimap_session_id(false);
2bd6b461 693 $sasl = (isset($capability['SASL-IR']) && $capability['SASL-IR']) ? true : false;
b98732e8 694 $auth = base64_encode("$username\0$username\0$password");
fe55c7c7 695 if ($sasl) {
696 // IMAP Extension for SASL Initial Client Response
2bd6b461 697 // <draft-siemborski-imap-sasl-initial-response-01b.txt>
fe55c7c7 698 $query = $tag . " AUTHENTICATE PLAIN $auth\r\n";
699 fputs($imap_stream, $query);
b98732e8 700 $read = sqimap_fgets($imap_stream);
fe55c7c7 701 } else {
702 $query = $tag . " AUTHENTICATE PLAIN\r\n";
703 fputs($imap_stream, $query);
704 $read=sqimap_fgets($imap_stream);
705 if (substr($read,0,1) == '+') { // OK so far..
706 fputs($imap_stream, "$auth\r\n");
707 $read = sqimap_fgets($imap_stream);
708 }
098ea084 709 }
b98732e8 710 $results=explode(" ",$read,3);
711 $response=$results[1];
712 $message=$results[2];
713 } else {
714 $response="BAD";
715 $message="Internal SquirrelMail error - unknown IMAP authentication method chosen. Please contact the developers.";
716 }
fe55c7c7 717
b98732e8 718 /* If the connection was not successful, lets see why */
9c737111 719 if ($response != 'OK') {
720 if (!$hide) {
74424a43 721 if ($response != 'NO') {
3411d4ec 722 /* "BAD" and anything else gets reported here. */
098ea084 723 $message = htmlspecialchars($message);
9c737111 724 set_up_language($squirrelmail_language, true);
098ea084 725 require_once(SM_PATH . 'functions/display_messages.php');
9c737111 726 if ($response == 'BAD') {
6fd95361 727 $string = sprintf (_("Bad request: %s")."<br />\r\n", $message);
9c737111 728 } else {
6fd95361 729 $string = sprintf (_("Unknown error: %s") . "<br />\n", $message);
9c737111 730 }
1e7fc1cb 731 if (isset($read) && is_array($read)) {
6fd95361 732 $string .= '<br />' . _("Read data:") . "<br />\n";
9c737111 733 foreach ($read as $line) {
6fd95361 734 $string .= htmlspecialchars($line) . "<br />\n";
9c737111 735 }
736 }
098ea084 737 error_box($string,$color);
9c737111 738 exit;
165e24a7 739 } else {
3411d4ec 740 /*
741 * If the user does not log in with the correct
1c72b151 742 * username and password it is not possible to get the
743 * correct locale from the user's preferences.
744 * Therefore, apply the same hack as on the login
745 * screen.
3411d4ec 746 *
747 * $squirrelmail_language is set by a cookie when
1c72b151 748 * the user selects language and logs out
bee165ef 749 */
fe55c7c7 750
9c737111 751 set_up_language($squirrelmail_language, true);
bd9c880b 752 include_once(SM_PATH . 'functions/display_messages.php' );
098ea084 753 sqsession_destroy();
d5c472f1 754 /* terminate the session nicely */
755 sqimap_logout($imap_stream);
bd9c880b 756 logout_error( _("Unknown user or password incorrect.") );
9c737111 757 exit;
052e0c26 758 }
9c737111 759 } else {
052e0c26 760 exit;
9c737111 761 }
762 }
9c737111 763 return $imap_stream;
764}
f1e6f580 765
48af4b64 766/**
767 * Simply logs out the IMAP session
768 * @param stream imap_stream the IMAP connection to log out.
769 * @return void
770 */
9c737111 771function sqimap_logout ($imap_stream) {
8d936b0c 772 /* Logout is not valid until the server returns 'BYE'
773 * If we don't have an imap_ stream we're already logged out */
26a2cc8b 774 if(isset($imap_stream) && $imap_stream)
8d936b0c 775 sqimap_run_command($imap_stream, 'LOGOUT', false, $response, $message);
9c737111 776}
777
48af4b64 778/**
779 * Retreive the CAPABILITY string from the IMAP server.
780 * If capability is set, returns only that specific capability,
781 * else returns array of all capabilities.
782 */
487daa81 783function sqimap_capability($imap_stream, $capability='') {
9c737111 784 global $sqimap_capabilities;
9c737111 785 if (!is_array($sqimap_capabilities)) {
1c72b151 786 $read = sqimap_run_command($imap_stream, 'CAPABILITY', true, $a, $b);
787
9c737111 788 $c = explode(' ', $read[0]);
789 for ($i=2; $i < count($c); $i++) {
790 $cap_list = explode('=', $c[$i]);
3411d4ec 791 if (isset($cap_list[1])) {
ecc92309 792 // FIX ME. capabilities can occure multiple times.
793 // THREAD=REFERENCES THREAD=ORDEREDSUBJECT
9c737111 794 $sqimap_capabilities[$cap_list[0]] = $cap_list[1];
3411d4ec 795 } else {
9c737111 796 $sqimap_capabilities[$cap_list[0]] = TRUE;
3411d4ec 797 }
f1e6f580 798 }
9c737111 799 }
487daa81 800 if ($capability) {
098ea084 801 if (isset($sqimap_capabilities[$capability])) {
802 return $sqimap_capabilities[$capability];
803 } else {
804 return false;
805 }
f1e6f580 806 }
487daa81 807 return $sqimap_capabilities;
9c737111 808}
809
48af4b64 810/**
811 * Returns the delimeter between mailboxes: INBOX/Test, or INBOX.Test
812 */
9c737111 813function sqimap_get_delimiter ($imap_stream = false) {
3411d4ec 814 global $sqimap_delimiter, $optional_delimiter;
85fc999e 815
9c737111 816 /* Use configured delimiter if set */
817 if((!empty($optional_delimiter)) && $optional_delimiter != 'detect') {
818 return $optional_delimiter;
819 }
85fc999e 820
9c737111 821 /* Do some caching here */
822 if (!$sqimap_delimiter) {
823 if (sqimap_capability($imap_stream, 'NAMESPACE')) {
3411d4ec 824 /*
825 * According to something that I can't find, this is supposed to work on all systems
826 * OS: This won't work in Courier IMAP.
827 * OS: According to rfc2342 response from NAMESPACE command is:
828 * OS: * NAMESPACE (PERSONAL NAMESPACES) (OTHER_USERS NAMESPACE) (SHARED NAMESPACES)
829 * OS: We want to lookup all personal NAMESPACES...
830 */
1c72b151 831 $read = sqimap_run_command($imap_stream, 'NAMESPACE', true, $a, $b);
f1e6f580 832 if (eregi('\\* NAMESPACE +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL)', $read[0], $data)) {
9c737111 833 if (eregi('^\\( *\\((.*)\\) *\\)', $data[1], $data2)) {
f1e6f580 834 $pn = $data2[1];
9c737111 835 }
f1e6f580 836 $pna = explode(')(', $pn);
9c737111 837 while (list($k, $v) = each($pna)) {
862ff2d3 838 $lst = explode('"', $v);
839 if (isset($lst[3])) {
840 $pn[$lst[1]] = $lst[3];
841 } else {
74424a43 842 $pn[$lst[1]] = '';
862ff2d3 843 }
f1e6f580 844 }
845 }
846 $sqimap_delimiter = $pn[0];
847 } else {
848 fputs ($imap_stream, ". LIST \"INBOX\" \"\"\r\n");
849 $read = sqimap_read_data($imap_stream, '.', true, $a, $b);
91e0dccc 850 $read = $read['.'][0]; //sqimap_read_data() now returns a tag array of response array
f1e6f580 851 $quote_position = strpos ($read[0], '"');
852 $sqimap_delimiter = substr ($read[0], $quote_position+1, 1);
853 }
854 }
855 return $sqimap_delimiter;
9c737111 856}
052e0c26 857
48af4b64 858/**
859 * This encodes a mailbox name for use in IMAP commands.
860 * @param string what the mailbox to encode
861 * @return string the encoded mailbox string
862 */
b2306cbe 863function sqimap_encode_mailbox_name($what)
864{
91e0dccc 865 if (ereg("[\"\\\r\n]", $what))
866 return '{' . strlen($what) . "}\r\n" . $what; /* 4.3 literal form */
867 return '"' . $what . '"'; /* 4.3 quoted string form */
b2306cbe 868}
869
48af4b64 870/**
871 * Gets the number of messages in the current mailbox.
1e0a2f0e 872 *
873 * OBSOLETE use sqimap_status_messages instead.
48af4b64 874 */
9c737111 875function sqimap_get_num_messages ($imap_stream, $mailbox) {
b2306cbe 876 $read_ary = sqimap_run_command ($imap_stream, 'EXAMINE ' . sqimap_encode_mailbox_name($mailbox), false, $result, $message);
9c737111 877 for ($i = 0; $i < count($read_ary); $i++) {
85fc999e 878 if (ereg("[^ ]+ +([^ ]+) +EXISTS", $read_ary[$i], $regs)) {
879 return $regs[1];
880 }
9c737111 881 }
1f720b34 882 return false; //"BUG! Couldn't get number of messages in $mailbox!";
9c737111 883}
1e0a2f0e 884include_once(SM_PATH . 'functions/rfc822address.php');
9c737111 885
1e0a2f0e 886/**
887 * OBSOLETE FUNCTION should be removed after mailbox_display,
888 * printMessage function is adapted
889 */
91688e5f 890function parseAddress($address, $max=0) {
1e0a2f0e 891 $aAddress = parseRFC822Address($address,array('limit'=> $max));
892 /*
893 * Because the expected format of the array element is changed we adapt it now.
894 * This also implies that this function is obsolete and should be removed after the
895 * rest of the source is adapted. See Rfc822Address.php for the new function.
896 */
897 array_walk($aAddress, '_adaptAddress');
898 return $aAddress;
899}
91688e5f 900
1e0a2f0e 901/**
902 * OBSOLETE FUNCTION should be removed after mailbox_display,
903 * printMessage function is adapted
904 */
905function _adaptAddress(&$aAddr,$k) {
906 $sPersonal = (isset($aAddr[SQM_ADDR_PERSONAL]) && $aAddr[SQM_ADDR_PERSONAL]) ?
907 $aAddr[SQM_ADDR_PERSONAL] : '';
908 $sEmail = ($aAddr[SQM_ADDR_HOST]) ?
3cd52f38 909 $aAddr[SQM_ADDR_MAILBOX] . '@'.$aAddr[SQM_ADDR_HOST] :
910 $aAddr[SQM_ADDR_MAILBOX];
1e0a2f0e 911 $aAddr = array($sEmail,$sPersonal);
912}
91688e5f 913
48af4b64 914/**
915 * Returns the number of unseen messages in this folder.
26e90c74 916 * obsoleted by sqimap_status_messages !
3411d4ec 917 */
9c737111 918function sqimap_unseen_messages ($imap_stream, $mailbox) {
26e90c74 919 $aStatus = sqimap_status_messages($imap_stream,$mailbox,array('UNSEEN'));
920 return $aStatus['UNSEEN'];
9c737111 921}
922
48af4b64 923/**
26e90c74 924 * Returns the status items of a mailbox.
925 * Default it returns MESSAGES,UNSEEN and RECENT
926 * Supported status items are MESSAGES, UNSEEN, RECENT, UIDNEXT and UIDVALIDITY
1f720b34 927 */
1e0a2f0e 928function sqimap_status_messages ($imap_stream, $mailbox,
26e90c74 929 $aStatusItems = array('MESSAGES','UNSEEN','RECENT')) {
930
3fedd15b 931 $aStatusItems = implode(' ',$aStatusItems);
26e90c74 932 $read_ary = sqimap_run_command ($imap_stream, 'STATUS ' . sqimap_encode_mailbox_name($mailbox) .
933 " ($aStatusItems)", false, $result, $message);
1f720b34 934 $i = 0;
26e90c74 935 $messages = $unseen = $recent = $uidnext = $uidvalidity = false;
1f720b34 936 $regs = array(false,false);
937 while (isset($read_ary[$i])) {
938 if (preg_match('/UNSEEN\s+([0-9]+)/i', $read_ary[$i], $regs)) {
098ea084 939 $unseen = $regs[1];
940 }
1f720b34 941 if (preg_match('/MESSAGES\s+([0-9]+)/i', $read_ary[$i], $regs)) {
098ea084 942 $messages = $regs[1];
943 }
b8ec18ef 944 if (preg_match('/RECENT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
098ea084 945 $recent = $regs[1];
1e0a2f0e 946 }
26e90c74 947 if (preg_match('/UIDNEXT\s+([0-9]+)/i', $read_ary[$i], $regs)) {
948 $uidnext = $regs[1];
1e0a2f0e 949 }
26e90c74 950 if (preg_match('/UIDVALIDITY\s+([0-9]+)/i', $read_ary[$i], $regs)) {
951 $uidvalidity = $regs[1];
1e0a2f0e 952 }
1f720b34 953 $i++;
954 }
1e0a2f0e 955 return array('MESSAGES' => $messages,
956 'UNSEEN'=>$unseen,
26e90c74 957 'RECENT' => $recent,
958 'UIDNEXT' => $uidnext,
959 'UIDVALIDITY' => $uidvalidity);
1f720b34 960}
961
9c737111 962
48af4b64 963/**
964 * Saves a message to a given folder -- used for saving sent messages
3411d4ec 965 */
9c737111 966function sqimap_append ($imap_stream, $sent_folder, $length) {
b2306cbe 967 fputs ($imap_stream, sqimap_session_id() . ' APPEND ' . sqimap_encode_mailbox_name($sent_folder) . " (\\Seen) \{$length}\r\n");
85fc999e 968 $tmp = fgets ($imap_stream, 1024);
7ec3a6b9 969 sqimap_append_checkresponse($tmp, $sent_folder);
9c737111 970}
971
8813fb15 972function sqimap_append_done ($imap_stream, $folder='') {
9c737111 973 fputs ($imap_stream, "\r\n");
974 $tmp = fgets ($imap_stream, 1024);
7ec3a6b9 975 sqimap_append_checkresponse($tmp, $folder);
976}
977
978function sqimap_append_checkresponse($response, $folder) {
979
980 if (preg_match("/(.*)(BAD|NO)(.*)$/", $response, $regs)) {
981 global $squirrelmail_language, $color;
69146537 982 set_up_language($squirrelmail_language);
1f720b34 983 require_once(SM_PATH . 'functions/display_messages.php');
7ec3a6b9 984
098ea084 985 $reason = $regs[3];
986 if ($regs[2] == 'NO') {
6fd95361 987 $string = "<b><font color=\"$color[2]\">\n" .
098ea084 988 _("ERROR : Could not append message to") ." $folder." .
6fd95361 989 "</b><br />\n" .
098ea084 990 _("Server responded: ") .
6fd95361 991 $reason . "<br />\n";
098ea084 992 if (preg_match("/(.*)(quota)(.*)$/i", $reason, $regs)) {
1e0a2f0e 993 $string .= _("Solution: ") .
994 _("Remove unneccessary messages from your folder and start with your Trash folder.")
6fd95361 995 ."<br />\n";
098ea084 996 }
997 $string .= "</font>\n";
998 error_box($string,$color);
999 } else {
6fd95361 1000 $string = "<b><font color=\"$color[2]\">\n" .
098ea084 1001 _("ERROR : Bad or malformed request.") .
6fd95361 1002 "</b><br />\n" .
098ea084 1003 _("Server responded: ") .
7ec3a6b9 1004 $reason . "</font><br />\n";
098ea084 1005 error_box($string,$color);
8813fb15 1006 exit;
098ea084 1007 }
69146537 1008 }
9c737111 1009}
85fc999e 1010
bd9829d7 1011function sqimap_get_user_server ($imap_server, $username) {
bd9829d7 1012 if (substr($imap_server, 0, 4) != "map:") {
1013 return $imap_server;
1014 }
bd9829d7 1015 $function = substr($imap_server, 4);
1016 return $function($username);
1017}
1018
48af4b64 1019/**
1020 * This is an example that gets imapservers from yellowpages (NIS).
1e0a2f0e 1021 * you can simple put map:map_yp_alias in your $imap_server_address
bd9829d7 1022 * in config.php use your own function instead map_yp_alias to map your
48af4b64 1023 * LDAP whatever way to find the users imapserver.
1024 */
bd9829d7 1025function map_yp_alias($username) {
1026 $yp = `ypmatch $username aliases`;
1027 return chop(substr($yp, strlen($username)+1));
1e0a2f0e 1028}
bd9829d7 1029
7ec3a6b9 1030?>