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