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