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