[ Index ] |
|
Code source de IMP H3 (4.1.5) |
1 <?php 2 3 define('IMP_IMAPCLIENT_TAGGED', 0); 4 define('IMP_IMAPCLIENT_UNTAGGED', 1); 5 define('IMP_IMAPCLIENT_CONTINUATION', 2); 6 7 /** 8 * The IMP_IMAPClient:: class enables connection to an IMAP server through 9 * built-in PHP functions. 10 * 11 * TODO: This should eventually be moved to Horde 4.0/framework. 12 * 13 * $Horde: imp/lib/IMAP/Client.php,v 1.21.2.32 2007/07/10 09:59:00 slusarz Exp $ 14 * 15 * Copyright 2005-2007 Michael Slusarz <slusarz@horde.org> 16 * 17 * Based on code from: 18 * + auth.php (1.49) 19 * + imap_general.php (1.212) 20 * + strings.php (1.184.2.35) 21 * from the Squirrelmail project. 22 * Copyright (c) 1999-2005 The SquirrelMail Project Team 23 * 24 * See the enclosed file COPYING for license information (GPL). If you 25 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. 26 * 27 * @author Michael Slusarz <slusarz@horde.org> 28 * @since IMP 4.1 29 * @package IMP 30 */ 31 class IMP_IMAPClient { 32 33 /** 34 * The list of capabilities of the IMAP server. 35 * 36 * @var array 37 */ 38 var $_capability = null; 39 40 /** 41 * The hostname of the IMAP server to connect to. 42 * 43 * @var string 44 */ 45 var $_host; 46 47 /** 48 * The namespace information. 49 * 50 * @var array 51 */ 52 var $_namespace = null; 53 54 /** 55 * The port number of the IMAP server to connect to. 56 * 57 * @var string 58 */ 59 var $_port; 60 61 /** 62 * The unique ID to use when making an IMAP query. 63 * 64 * @var integer 65 */ 66 var $_sessionid = 1; 67 68 /** 69 * The currently active tag. 70 * 71 * @var string 72 */ 73 var $_currtag = null; 74 75 /** 76 * The socket connection to the IMAP server. 77 * 78 * @var resource 79 */ 80 var $_stream; 81 82 /** 83 * Are we using SSL to connect to the IMAP server? 84 * 85 * @var string 86 */ 87 var $_usessl = false; 88 89 /** 90 * Are we using TLS to connect to the IMAP server? 91 * 92 * @var string 93 */ 94 var $_usetls = false; 95 96 /** 97 * Constructor. 98 * 99 * @param string $host The address/hostname of the IMAP server. 100 * @param string $port The port to connect to on the IMAP server. 101 * @param string $protocol The protocol string (See, e.g., servers.php). 102 */ 103 function IMP_IMAPClient($host, $port, $protocol) 104 { 105 $this->_host = $host; 106 $this->_port = $port; 107 108 /* Split apart protocol string to discover if we need to use either 109 * SSL or TLS. */ 110 $tmp = explode('/', strtolower($protocol)); 111 if (in_array('tls', $tmp)) { 112 $this->_usetls = true; 113 } elseif (in_array('ssl', $tmp)) { 114 $this->_usessl = true; 115 } 116 } 117 118 /** 119 * Are we using TLS to connect and is it supported? 120 * 121 * @return mixed Returns true if TLS is being used to connect, false if 122 * is not, and PEAR_Error if we are attempting to use TLS 123 * and this version of PHP doesn't support it. 124 */ 125 function useTLS() 126 { 127 if ($this->_usetls) { 128 /* There is no way in PHP 4 to open a TLS connection to a 129 * non-secured port. See http://bugs.php.net/bug.php?id=26040 */ 130 if (!function_exists('stream_socket_enable_crypto')) { 131 return PEAR::raiseError(_("To use a TLS connection, you must be running a version of PHP 5.1.0 or higher."), 'horde.error'); 132 } 133 } 134 135 return $this->_usetls; 136 } 137 138 /** 139 * Generates a new IMAP session ID by incrementing the last one used. 140 * 141 * @access private 142 * 143 * @return string IMAP session id of the form 'A000'. 144 */ 145 function _generateSid() 146 { 147 return sprintf("A%03d", $this->_sessionid++); 148 } 149 150 /** 151 * Perform a command on the IMAP server. 152 * 153 * @access private 154 * 155 * @param string $query The IMAP command to execute. 156 * 157 * @return stdClass Returns PEAR_Error on error. On success, returns 158 * a stdClass object with the following elements: 159 * <pre> 160 * 'message' - The response message 161 * 'response' - The response code 162 * 'type' - Either IMP_IMAPCLIENT_TAGGED, IMP_IMAPCLIENT_UNTAGGED, or 163 * IMP_IMAPCLIENT_CONTINUATION 164 * </pre> 165 */ 166 function _runCommand($query) 167 { 168 if (!$this->_currtag) { 169 $this->_currtag = $this->_generateSid(); 170 $query = $this->_currtag . ' ' . $query; 171 } 172 173 fwrite($this->_stream, $query . "\r\n"); 174 $ob = $this->_parseLine(); 175 if (is_a($ob, 'PEAR_Error')) { 176 $this->_currtag = null; 177 return $ob; 178 } 179 180 switch ($ob->response) { 181 case 'OK': 182 break; 183 184 case 'NO': 185 /* Ignore this error from M$ exchange, it is not fatal (aka 186 * bug). */ 187 if (strstr($ob->message, 'command resulted in') === false) { 188 $this->_currtag = null; 189 return PEAR::raiseError(sprintf(_("Could not complete request. Reason Given: %s"), $ob->message), 'horde.error', null, null, $ob->response); 190 } 191 break; 192 193 case 'BAD': 194 $this->_currtag = null; 195 return PEAR::raiseError(sprintf(_("Bad or malformed request. Server Responded: %s"), $ob->message), 'horde.error', null, null, $ob->response); 196 break; 197 198 case 'BYE': 199 $this->_currtag = null; 200 return PEAR::raiseError(sprintf(_("IMAP Server closed the connection. Server Responded: %s"), $ob->message), 'horde.error', null, null, $ob->response); 201 break; 202 203 default: 204 $this->_currtag = null; 205 return PEAR::raiseError(sprintf(_("Unknown IMAP response from the server. Server Responded: %s"), $ob->message), 'horde.error', null, null, $ob->response); 206 break; 207 } 208 209 if ($ob->type != IMP_IMAPCLIENT_CONTINUATION) { 210 $this->_currtag = null; 211 } 212 213 return $ob; 214 } 215 216 /** 217 * TODO 218 * 219 * @access private 220 * 221 * @return stdClass See _runCommand(). 222 */ 223 function _parseLine() 224 { 225 $ob = new stdClass; 226 $read = explode(' ', trim(fgets($this->_stream)), 2); 227 228 switch ($read[0]) { 229 /* Continuation response. */ 230 case '+': 231 $ob->message = isset($read[1]) ? trim($read[1]) : ''; 232 $ob->response = 'OK'; 233 $ob->type = IMP_IMAPCLIENT_CONTINUATION; 234 break; 235 236 /* Untagged response. */ 237 case '*': 238 $tmp = explode(' ', $read[1], 2); 239 $ob->response = trim($tmp[0]); 240 if (in_array($ob->response, array('OK', 'NO', 'BAD', 'PREAUTH', 'BYE'))) { 241 $ob->message = trim($tmp[1]); 242 } else { 243 $ob->response = 'OK'; 244 $ob->message = $read[1]; 245 } 246 $ob->type = IMP_IMAPCLIENT_UNTAGGED; 247 $ob2 = $this->_parseLine(); 248 if ($ob2->response != 'OK') { 249 $ob = $ob2; 250 } elseif ($ob2->type == IMP_IMAPCLIENT_UNTAGGED) { 251 $ob->message .= "\n" . $ob2->message; 252 } 253 break; 254 255 /* Tagged response. */ 256 default: 257 $tmp = explode(' ', $read[1], 2); 258 $ob->type = IMP_IMAPCLIENT_TAGGED; 259 if ($this->_currtag && ($read[0] == $this->_currtag)) { 260 $ob->message = trim($tmp[1]); 261 $ob->response = trim($tmp[0]); 262 } else { 263 $ob->message = $read[0]; 264 $ob->response = ''; 265 } 266 break; 267 } 268 269 return $ob; 270 } 271 272 /** 273 * Connects to the IMAP server. 274 * 275 * @access private 276 * 277 * @return mixed Returns true on success, PEAR_Error on error. 278 */ 279 function _createStream() 280 { 281 if (($this->_usessl || $this->_usetls) && 282 !Util::extensionExists('openssl')) { 283 return PEAR::raiseError(_("If using SSL or TLS, you must have the PHP openssl extension loaded."), 'horde.error'); 284 } 285 286 if ($res = $this->useTLS()) { 287 if (is_a($res, 'PEAR_Error')) { 288 return $res; 289 } else { 290 $this->_host = 'tcp://' . $this->_host . ':' . $this->_port; 291 } 292 } 293 294 if ($this->_usessl) { 295 $this->_host = 'ssl://' . $this->_host; 296 } 297 $error_number = $error_string = ''; 298 $timeout = 10; 299 300 if ($this->_usetls) { 301 $this->_stream = stream_socket_client($this->_host, $error_number, $error_string, $timeout); 302 if (!$this->_stream) { 303 return PEAR::raiseError(sprintf(_("Error connecting to IMAP server: [%s] %s."), $error_number, $error_string), 'horde.error'); 304 } 305 306 /* Disregard any server information returned. */ 307 fgets($this->_stream); 308 309 /* Send the STARTTLS command. */ 310 $res = $this->_runCommand('STARTTLS'); 311 312 /* Switch over to a TLS connection. */ 313 if (!is_a($res, 'PEAR_Error')) { 314 $res = stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); 315 } 316 if (!$res || is_a($res, 'PEAR_Error')) { 317 $this->logout(); 318 return PEAR::raiseError(_("Could not open secure connection to the IMAP server."), 'horde.error'); 319 } 320 } else { 321 $this->_stream = fsockopen($this->_host, $this->_port, $error_number, $error_string, $timeout); 322 if (!$this->_stream) { 323 return PEAR::raiseError(sprintf(_("Error connecting to IMAP server: [%s] %s."), $error_number, $error_string), 'horde.error'); 324 } 325 326 /* Disregard server information. */ 327 fgets($this->_stream); 328 } 329 330 register_shutdown_function(array(&$this, 'logout')); 331 } 332 333 /** 334 * Log the user into the IMAP server. 335 * 336 * @param string $username Username. 337 * @param string $password Encrypted password. 338 * 339 * @return mixed True on success, PEAR_Error on error. 340 */ 341 function login($username, $password) 342 { 343 $res = $this->_createStream(); 344 if (is_a($res, 'PEAR_Error')) { 345 Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR); 346 return $res; 347 } 348 349 $imap_auth_mech = array(); 350 351 /* Use md5 authentication, if available. But no need to use special 352 * authentication if we are already using an encrypted connection. */ 353 $auth_methods = $this->queryCapability('AUTH'); 354 if (!$this->_usessl && !$this->_usetls && !empty($auth_methods)) { 355 if (in_array('CRAM-MD5', $auth_methods)) { 356 $imap_auth_mech[] = 'cram-md5'; 357 } 358 if (in_array('DIGEST-MD5', $auth_methods)) { 359 $imap_auth_mech[] = 'digest-md5'; 360 } 361 } 362 363 /* Next, try 'PLAIN' authentication. */ 364 if (!empty($auth_methods) && in_array('PLAIN', $auth_methods)) { 365 $imap_auth_mech[] = 'plain'; 366 } 367 368 /* Fall back to 'LOGIN' if available. */ 369 if (!$this->queryCapability('LOGINDISABLED')) { 370 $imap_auth_mech[] = 'login'; 371 } 372 373 if (empty($imap_auth_mech)) { 374 return PEAR::raiseError(_("No supported IMAP authentication method could be found."), 'horde.error'); 375 } 376 377 foreach ($imap_auth_mech as $method) { 378 $res = $this->_login($username, $password, $method); 379 if (!is_a($res, 'PEAR_Error')) { 380 return true; 381 } else { 382 Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_WARNING); 383 } 384 } 385 386 return $res; 387 } 388 389 /** 390 * Log the user into the IMAP server. 391 * 392 * @access private 393 * 394 * @param string $username Username. 395 * @param string $password Encrypted password. 396 * @param string $method IMAP login method. 397 * 398 * @return mixed True on success, PEAR_Error on error. 399 */ 400 function _login($username, $password, $method) 401 { 402 switch ($method) { 403 case 'cram-md5': 404 case 'digest-md5': 405 /* If we don't have Auth_SASL package install, return error. */ 406 if (!@include_once 'Auth/SASL.php') { 407 return PEAR::raiseError(_("CRAM-MD5 or DIGEST-MD5 requires PEAR's Auth_SASL package to be installed."), 'horde.error'); 408 } 409 410 $res = $this->_runCommand('AUTHENTICATE ' . $method); 411 if (is_a($res, 'PEAR_Error')) { 412 return $res; 413 } 414 415 if ($method == 'cram-md5') { 416 $auth_sasl = Auth_SASL::factory('crammd5'); 417 $response = $auth_sasl->getResponse($username, $password, base64_decode($res->message)); 418 $read = $this->_runCommand(base64_encode($response)); 419 } elseif ($method == 'digest-md5') { 420 $auth_sasl = Auth_SASL::factory('digestmd5'); 421 $response = $auth_sasl->getResponse($username, $password, base64_decode($res->message), $this->_host, 'imap'); 422 $res = $this->_runCommand(base64_encode($response)); 423 if (is_a($res, 'PEAR_Error')) { 424 return $res; 425 } 426 $response = base64_decode($res->message); 427 if (strpos($response, 'rspauth=') === false) { 428 return PEAR::raiseError(_("Unexpected response from server to Digest-MD5 response."), 'horde.error'); 429 } 430 $read = $this->_runCommand(''); 431 } else { 432 return PEAR::raiseError(_("The IMAP server does not appear to support the authentication method selected. Please contact your system administrator."), 'horde.error'); 433 } 434 break; 435 436 case 'login': 437 /* We should use a literal string to send the username, but some 438 * IMAP servers don't support a literal string request inside of a 439 * literal string. Thus, use a quoted string for the username 440 * (which should probably be OK since it is very unlikely a 441 * username will include a double-quote character). */ 442 $read = $this->_runCommand("LOGIN \"$username\" {" . strlen($password) . "}"); 443 if (!is_a($read, 'PEAR_Error') && 444 ($read->type == IMP_IMAPCLIENT_CONTINUATION)) { 445 $read = $this->_runCommand($password); 446 } 447 break; 448 449 case 'plain': 450 $sasl = $this->queryCapability('SASL-IR'); 451 $auth = base64_encode("$username\0$username\0$password"); 452 if ($sasl) { 453 // IMAP Extension for SASL Initial Client Response 454 // <draft-siemborski-imap-sasl-initial-response-01b.txt> 455 $read = $this->_runCommand("AUTHENTICATE PLAIN $auth"); 456 } else { 457 $read = $this->_runCommand("AUTHENTICATE PLAIN"); 458 if (!is_a($read, 'PEAR_Error') && 459 ($read->type == IMP_IMAPCLIENT_CONTINUATION)) { 460 $read = $this->_runCommand($auth); 461 } else { 462 return PEAR::raiseError(_("Unexpected response from server to AUTHENTICATE command."), 'horde.error'); 463 } 464 } 465 break; 466 } 467 468 if (is_a($read, 'PEAR_Error')) { 469 return $read; 470 } 471 472 /* Check for failed login. */ 473 if ($read->response != 'OK') { 474 $message = !empty($read->message) ? htmlspecialchars($read->message) : _("No message returned."); 475 476 switch ($read->response) { 477 case 'NO': 478 return PEAR::raiseError(sprintf(_("Bad login name or password."), $message), 'horde.error'); 479 480 case 'BAD': 481 default: 482 return PEAR::raiseError(sprintf(_("Bad request: %s"), $message), 'horde.error'); 483 } 484 } 485 486 return true; 487 } 488 489 /** 490 * Log out of the IMAP session. 491 */ 492 function logout() 493 { 494 if (!empty($this->_stream)) { 495 $this->_runCommand('LOGOUT'); 496 fclose($this->_stream); 497 } 498 } 499 500 /** 501 * Get the CAPABILITY string from the IMAP server. 502 * 503 * @access private 504 */ 505 function _capability() 506 { 507 if (!is_null($this->_capability)) { 508 return; 509 } 510 511 $this->_capability = array(); 512 $read = $this->_runCommand('CAPABILITY'); 513 if (is_a($read, 'PEAR_Error')) { 514 Horde::logMessage($read, __FILE__, __LINE__, PEAR_LOG_ERR); 515 return; 516 } 517 518 $c = explode(' ', $read->message); 519 for ($i = 2; $i < count($c); $i++) { 520 $cap_list = explode('=', $c[$i]); 521 if (isset($cap_list[1])) { 522 if (!isset($this->_capability[$cap_list[0]]) || 523 !is_array($this->_capability[$cap_list[0]])) { 524 $this->_capability[$cap_list[0]] = array(); 525 } 526 $this->_capability[$cap_list[0]][] = $cap_list[1]; 527 } elseif (!isset($this->_capability[$cap_list[0]])) { 528 $this->_capability[$cap_list[0]] = true; 529 } 530 } 531 } 532 533 /** 534 * Returns whether the IMAP server supports the given capability. 535 * 536 * @param string $capability The capability string to query. 537 * 538 * @param mixed True if the server supports the queried capability, 539 * false if it doesn't, or an array if the capability can 540 * contain multiple values. 541 */ 542 function queryCapability($capability) 543 { 544 $this->_capability(); 545 return isset($this->_capability[$capability]) ? $this->_capability[$capability] : false; 546 } 547 548 /** 549 * Get the NAMESPACE information from the IMAP server. 550 * 551 * @param array $additional If the server supports namespaces, any 552 * additional namespaces to add to the 553 * namespace list that are not broadcast by 554 * the server. 555 * 556 * @return array An array with the following format: 557 * <pre> 558 * Array 559 * ( 560 * [foo1] => Array 561 * ( 562 * [name] => (string) 563 * [delimiter] => (string) 564 * [type] => [personal|others|shared] (string) 565 * [hidden] => (boolean) 566 * ) 567 * 568 * [foo2] => Array 569 * ( 570 * ... 571 * ) 572 * ) 573 * </pre> 574 * Returns PEAR_Error object on error. 575 */ 576 function namespace($additional = array()) 577 { 578 if (!is_null($this->_namespace)) { 579 return $this->_namespace; 580 } 581 582 $namespace_array = array( 583 1 => 'personal', 584 2 => 'others', 585 3 => 'shared' 586 ); 587 588 if ($this->queryCapability('NAMESPACE')) { 589 /* According to RFC 2342, response from NAMESPACE command is: 590 * * NAMESPACE (PERSONAL NAMESPACES) (OTHER_USERS NAMESPACE) (SHARED NAMESPACES) 591 */ 592 $read = $this->_runCommand('NAMESPACE'); 593 if (is_a($read, 'PEAR_Error')) { 594 Horde::logMessage($read, __FILE__, __LINE__, PEAR_LOG_ERR); 595 return $read; 596 } 597 598 if (($read->type == IMP_IMAPCLIENT_UNTAGGED) && 599 eregi('NAMESPACE +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL)', $read->message, $data)) { 600 for ($i = 1; $i <= 3; $i++) { 601 if ($data[$i] == 'NIL') { 602 continue; 603 } 604 $pna = explode(')(', $data[$i]); 605 while (list($k, $v) = each($pna)) { 606 $lst = explode('"', $v); 607 $delimiter = (isset($lst[3])) ? $lst[3] : ''; 608 $this->_namespace[$lst[1]] = array('name' => $lst[1], 'delimiter' => $delimiter, 'type' => $namespace_array[$i], 'hidden' => false); 609 } 610 } 611 } 612 613 foreach ($additional as $val) { 614 /* Skip namespaces if we have already auto-detected them. 615 * Also, hidden namespaces cannot be empty. */ 616 $val = trim($val); 617 if (empty($val) || isset($this->_namespace[$val])) { 618 continue; 619 } 620 $read = $this->_runCommand('LIST "" "' . $val . '"'); 621 if (is_a($read, 'PEAR_Error')) { 622 Horde::logMessage($read, __FILE__, __LINE__, PEAR_LOG_ERR); 623 return $read; 624 } 625 if (($read->type == IMP_IMAPCLIENT_UNTAGGED) && 626 preg_match("/^LIST \(.*\) \"(.*)\" \"?(.*?)\"?\s*$/", $read->message, $data) && 627 ($data[2] == $val)) { 628 $this->_namespace[$val] = array('name' => $val, 'delimiter' => $data[1], 'type' => $namespace_array[3], 'hidden' => true); 629 } 630 } 631 } 632 633 if (empty($this->_namespace)) { 634 $res = $this->_runCommand('LIST "" ""'); 635 if (is_a($res, 'PEAR_Error')) { 636 Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR); 637 return $res; 638 } 639 $quote_position = strpos($res->message, '"'); 640 $this->_namespace[''] = array('name' => '', 'delimiter' => substr($res->message, $quote_position + 1 , 1), 'type' => $namespace_array[1], 'hidden' => false); 641 } 642 643 return $this->_namespace; 644 } 645 646 /** 647 * Determines whether the IMAP search command supports the optional 648 * charset provided. 649 * 650 * @param string $charset The character set to test. 651 * 652 * @return boolean True if the IMAP search command supports the charset. 653 */ 654 function searchCharset($charset) 655 { 656 $read = $this->_runCommand('SELECT INBOX'); 657 if (!is_a($read, 'PEAR_Error')) { 658 $read = $this->_runCommand('SEARCH CHARSET ' . $charset . ' TEXT "charsettest" 1'); 659 } 660 return !is_a($read, 'PEAR_Error'); 661 } 662 663 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Thu Nov 29 12:30:07 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |