[ Index ]
 

Code source de IMP H3 (4.1.5)

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/lib/IMAP/ -> Client.php (source)

   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  }


Généré le : Thu Nov 29 12:30:07 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics