mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-27 00:58:56 +01:00
790 lines
27 KiB
PHP
790 lines
27 KiB
PHP
|
<?php
|
||
|
|
||
|
include_once('PEAR.php');
|
||
|
|
||
|
/**
|
||
|
* The IMP_IMAPClient:: class enables connection to an IMAP server through
|
||
|
* built-in PHP functions.
|
||
|
*
|
||
|
* TODO: This should eventually be moved to Horde 4.0/framework.
|
||
|
*
|
||
|
* $Horde: imp/lib/IMAP/Client.php,v 1.21.2.21 2006/03/30 10:15:31 selsky Exp $
|
||
|
*
|
||
|
* Copyright 2005-2006 Michael Slusarz <slusarz@horde.org>
|
||
|
*
|
||
|
* Based on code from:
|
||
|
* + auth.php (1.49)
|
||
|
* + imap_general.php (1.212)
|
||
|
* + strings.php (1.184.2.35)
|
||
|
* from the Squirrelmail project.
|
||
|
* Copyright (c) 1999-2005 The SquirrelMail Project Team
|
||
|
*
|
||
|
* See the enclosed file COPYING for license information (GPL). If you
|
||
|
* did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
|
||
|
*
|
||
|
* @author Michael Slusarz <slusarz@horde.org>
|
||
|
* @since IMP 4.1
|
||
|
* @package IMP
|
||
|
*/
|
||
|
class imap_client {
|
||
|
|
||
|
/**
|
||
|
* The list of capabilities of the IMAP server.
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
var $_capability = null;
|
||
|
|
||
|
/**
|
||
|
* The hostname of the IMAP server to connect to.
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_host;
|
||
|
|
||
|
/**
|
||
|
* The last message returned from the server.
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_message;
|
||
|
|
||
|
/**
|
||
|
* The namespace information.
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
var $_namespace = null;
|
||
|
|
||
|
/**
|
||
|
* The port number of the IMAP server to connect to.
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_port;
|
||
|
|
||
|
/**
|
||
|
* The last response returned from the server.
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_response;
|
||
|
|
||
|
/**
|
||
|
* The unique session identifier ID to use when making an IMAP query.
|
||
|
*
|
||
|
* @var integer
|
||
|
*/
|
||
|
var $_sessionid = 1;
|
||
|
|
||
|
/**
|
||
|
* The socket connection to the IMAP server.
|
||
|
*
|
||
|
* @var resource
|
||
|
*/
|
||
|
var $_stream;
|
||
|
|
||
|
/**
|
||
|
* Are we using SSL to connect to the IMAP server?
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_usessl = false;
|
||
|
|
||
|
/**
|
||
|
* Are we using TLS to connect to the IMAP server?
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_usetls = false;
|
||
|
|
||
|
/**
|
||
|
* Constructor.
|
||
|
*
|
||
|
* @param string $host The address/hostname of the IMAP server.
|
||
|
* @param string $port The port to connect to on the IMAP server.
|
||
|
* @param string $protocol The protocol string (See, e.g., servers.php).
|
||
|
*/
|
||
|
function imap_client($host, $port, $protocol)
|
||
|
{
|
||
|
$this->_host = $host;
|
||
|
$this->_port = $port;
|
||
|
|
||
|
/* Split apart protocol string to discover if we need to use either
|
||
|
* SSL or TLS. */
|
||
|
$tmp = explode('/', strtolower($protocol));
|
||
|
if (in_array('tls', $tmp)) {
|
||
|
$this->_usetls = true;
|
||
|
} elseif (in_array('ssl', $tmp)) {
|
||
|
$this->_usessl = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Are we using TLS to connect and is it supported?
|
||
|
*
|
||
|
* @return mixed Returns true if TLS is being used to connect, false if
|
||
|
* is not, and PEAR_Error if we are attempting to use TLS
|
||
|
* and this version of PHP doesn't support it.
|
||
|
*/
|
||
|
function useTLS()
|
||
|
{
|
||
|
if ($this->_usetls) {
|
||
|
/* There is no way in PHP 4 to open a TLS connection to a
|
||
|
* non-secured port. See http://bugs.php.net/bug.php?id=26040 */
|
||
|
if (!function_exists('stream_socket_enable_crypto')) {
|
||
|
return PEAR::raiseError(lang("To use a TLS connection, you must be running a version of PHP 5.1.0 or higher."), 'horde.error');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $this->_usetls;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates a new IMAP session ID by incrementing the last one used.
|
||
|
*
|
||
|
* @access private
|
||
|
*
|
||
|
* @return string IMAP session id of the form 'A000'.
|
||
|
*/
|
||
|
function _generateSid()
|
||
|
{
|
||
|
return sprintf("A%03d", $this->_sessionid++);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Perform a command on the IMAP server.
|
||
|
* This command sets the $_response and $_message variable.
|
||
|
*
|
||
|
* @access private
|
||
|
*
|
||
|
* @param string $query IMAP command.
|
||
|
*
|
||
|
* @return mixed Returns PEAR_Error on error. On success, returns an
|
||
|
* array of the IMAP return text.
|
||
|
*/
|
||
|
function _runCommand($query)
|
||
|
{
|
||
|
$message = $response = array();
|
||
|
|
||
|
$sid = $this->_generateSid();
|
||
|
fwrite($this->_stream, $sid . ' ' . $query . "\r\n");
|
||
|
$tag_uid_a = explode(' ', trim($sid));
|
||
|
$tag = $tag_uid_a[0];
|
||
|
|
||
|
$res = $this->_retrieveIMAPResponse($tag, $response, $message);
|
||
|
if (is_a($res, 'PEAR_Error')) {
|
||
|
$this->_message = $this->_response = '';
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
/* retrieve the response and the message */
|
||
|
$this->_response = $response[$tag];
|
||
|
$this->_message = $message[$tag];
|
||
|
|
||
|
return (!empty($res[$tag])) ? $res[$tag][0] : $res[$tag];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Custom fgets function - get a line from the IMAP server no matter how
|
||
|
* large the line may be.
|
||
|
*
|
||
|
* @access private
|
||
|
*
|
||
|
* @return string The next line in the IMAP stream.
|
||
|
*/
|
||
|
function _fgets()
|
||
|
{
|
||
|
$buffer = 4096;
|
||
|
$offset = 0;
|
||
|
$results = '';
|
||
|
|
||
|
while (strpos($results, "\r\n", $offset) === false) {
|
||
|
if (!($read = fgets($this->_stream, $buffer))) {
|
||
|
$results = '';
|
||
|
break;
|
||
|
}
|
||
|
if ($results != '') {
|
||
|
$offset = strlen($results) - 1;
|
||
|
}
|
||
|
$results .= $read;
|
||
|
}
|
||
|
|
||
|
return $results;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads the output from the IMAP stream.
|
||
|
*
|
||
|
* @access private
|
||
|
*
|
||
|
* @param string $tag The IMAP SID tag.
|
||
|
* @param array $response The response information.
|
||
|
* @param array $message The message information.
|
||
|
*
|
||
|
* @return mixed PEAR_Error on error, response string on success.
|
||
|
*/
|
||
|
function _retrieveIMAPResponse($tag, &$response, &$message)
|
||
|
{
|
||
|
$aResponse = $read = '';
|
||
|
$data = $resultlist = array();
|
||
|
$i = 0;
|
||
|
|
||
|
$read = $this->_fgets();
|
||
|
while ($read) {
|
||
|
$char = $read{0};
|
||
|
switch ($char) {
|
||
|
case '+':
|
||
|
default:
|
||
|
$read = $this->_fgets();
|
||
|
break;
|
||
|
|
||
|
case $tag{0}:
|
||
|
/* Get the command. */
|
||
|
$arg = '';
|
||
|
$i = strlen($tag) + 1;
|
||
|
$s = substr($read, $i);
|
||
|
if (($j = strpos($s, ' ')) || ($j = strpos($s, "\n"))) {
|
||
|
$arg = substr($s, 0, $j);
|
||
|
}
|
||
|
$found_tag = substr($read, 0, $i - 1);
|
||
|
if ($found_tag) {
|
||
|
$response[$found_tag] = $arg;
|
||
|
$message[$found_tag] = trim(substr($read, $i + strlen($arg)));
|
||
|
if (!empty($data)) {
|
||
|
$resultlist[] = $data;
|
||
|
}
|
||
|
$aResponse[$found_tag] = $resultlist;
|
||
|
$data = $resultlist = array();
|
||
|
if ($found_tag == $tag) {
|
||
|
break 2;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$read = $this->_fgets();
|
||
|
if ($read === false) {
|
||
|
break 2; /* switch while */
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '*':
|
||
|
if (preg_match('/^\*\s\d+\sFETCH/', $read)) {
|
||
|
/* check for literal */
|
||
|
$s = substr($read, -3);
|
||
|
$fetch_data = array();
|
||
|
do {
|
||
|
/* Outer loop: continue until next untagged fetch
|
||
|
or tagged reponse. */
|
||
|
do {
|
||
|
/* Innerloop for fetching literals. with this
|
||
|
loop we prohibit that literal responses appear
|
||
|
in the outer loop so we can trust the untagged
|
||
|
and tagged info provided by $read. */
|
||
|
if ($s === "}\r\n") {
|
||
|
$j = strrpos($read, '{');
|
||
|
$iLit = substr($read, $j + 1, -3);
|
||
|
$fetch_data[] = $read;
|
||
|
$sLiteral = fread($this->_stream, $iLit);
|
||
|
if ($sLiteral === false) { /* error */
|
||
|
break 4; /* while while switch while */
|
||
|
}
|
||
|
/* backwards compattibility */
|
||
|
$aLiteral = explode("\n", $sLiteral);
|
||
|
|
||
|
unset($sLiteral);
|
||
|
|
||
|
foreach ($aLiteral as $line) {
|
||
|
$fetch_data[] = $line ."\n";
|
||
|
}
|
||
|
|
||
|
unset($aLiteral);
|
||
|
|
||
|
/* Next fgets belongs to this fetch because
|
||
|
we just got the exact literalsize and data
|
||
|
must follow to complete the response. */
|
||
|
$read = $this->_fgets();
|
||
|
if ($read === false) { /* error */
|
||
|
break 4; /* while while switch while */
|
||
|
}
|
||
|
}
|
||
|
$fetch_data[] = $read;
|
||
|
|
||
|
/* Retrieve next line and check in the while
|
||
|
statements if it belongs to this fetch
|
||
|
response. */
|
||
|
$read = $this->_fgets();
|
||
|
if ($read === false) { /* error */
|
||
|
break 4; /* while while switch while */
|
||
|
}
|
||
|
/* Check for next untagged reponse and break. */
|
||
|
if ($read{0} == '*') {
|
||
|
break 2;
|
||
|
}
|
||
|
$s = substr($read, -3);
|
||
|
} while ($s === "}\r\n");
|
||
|
|
||
|
$s = substr($read,-3);
|
||
|
} while (($read{0} !== '*') &&
|
||
|
(substr($read, 0, strlen($tag)) !== $tag));
|
||
|
|
||
|
$resultlist[] = $fetch_data;
|
||
|
unset($fetch_data);
|
||
|
} else {
|
||
|
$s = substr($read, -3);
|
||
|
do {
|
||
|
if ($s === "}\r\n") {
|
||
|
$j = strrpos($read, '{');
|
||
|
$iLit = substr($read, $j + 1, -3);
|
||
|
$data[] = $read;
|
||
|
$sLiteral = fread($this->_stream, $iLit);
|
||
|
if ($sLiteral === false) { /* error */
|
||
|
$read = false;
|
||
|
break 3; /* while switch while */
|
||
|
}
|
||
|
$data[] = $sLiteral;
|
||
|
$data[] = $this->_fgets();
|
||
|
} else {
|
||
|
$data[] = $read;
|
||
|
}
|
||
|
$read = $this->_fgets();
|
||
|
if ($read === false) {
|
||
|
break 3; /* while switch while */
|
||
|
} elseif ($read{0} == '*') {
|
||
|
break;
|
||
|
}
|
||
|
$s = substr($read,-3);
|
||
|
} while ($s === "}\r\n");
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Error processing in case $read is false. */
|
||
|
if ($read === false) {
|
||
|
/* Try to retrieve an untagged bye respons from the results. */
|
||
|
$sResponse = array_pop($data);
|
||
|
if (($sResponse !== NULL) &&
|
||
|
(strpos($sResponse,'* BYE') !== false)) {
|
||
|
return PEAR::raiseError(lang("IMAP server closed the connection."), 'horde.error');
|
||
|
} else {
|
||
|
return PEAR::raiseError(lang("Connection dropped by IMAP server."), 'horde.error');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch ($response[$tag]) {
|
||
|
case 'OK':
|
||
|
return $aResponse;
|
||
|
break;
|
||
|
|
||
|
case 'NO':
|
||
|
/* Ignore this error from M$ exchange, it is not fatal (aka bug). */
|
||
|
if (strpos($message[$tag], 'command resulted in') === false) {
|
||
|
return PEAR::raiseError(sprintf(lang("Could not complete request. Reason Given: %s"), $message[$tag]), 'horde.error', null, null, $response[$tag]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'BAD':
|
||
|
return PEAR::raiseError(sprintf(lang("Bad or malformed request. Server Responded: %s"), $message[$tag]), 'horde.error', null, null, $response[$tag]);
|
||
|
break;
|
||
|
|
||
|
case 'BYE':
|
||
|
return PEAR::raiseError(sprintf(lang("IMAP Server closed the connection. Server Responded: %s"), $message[$tag]), 'horde.error', null, null, $response[$tag]);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return PEAR::raiseError(sprintf(lang("Unknown IMAP response from the server. Server Responded: %s"), $message[$tag]), 'horde.error', null, null, $response[$tag]);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Connects to the IMAP server.
|
||
|
*
|
||
|
* @access private
|
||
|
*
|
||
|
* @return mixed Returns true on success, PEAR_Error on error.
|
||
|
*/
|
||
|
function _createStream()
|
||
|
{
|
||
|
if (($this->_usessl || $this->_usetls) &&
|
||
|
!function_exists('openssl_pkcs7_sign')) {
|
||
|
return PEAR::raiseError(lang("If using SSL or TLS, you must have the PHP openssl extension loaded."), 'horde.error');
|
||
|
}
|
||
|
|
||
|
if ($res = $this->useTLS()) {
|
||
|
if (is_a($res, 'PEAR_Error')) {
|
||
|
return $res;
|
||
|
} else {
|
||
|
$this->_host = $this->_host . ':' . $this->_port;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($this->_usessl) {
|
||
|
$this->_host = 'ssl://' . $this->_host;
|
||
|
}
|
||
|
$error_number = $error_string = '';
|
||
|
$timeout = 10;
|
||
|
|
||
|
if ($this->_usetls) {
|
||
|
$this->_stream = stream_socket_client($this->_host, $error_number, $error_string, $timeout);
|
||
|
if (!$this->_stream) {
|
||
|
return PEAR::raiseError(sprintf(lang("Error connecting to IMAP server. %s : %s."), $error_number, $error_string), 'horde.error');
|
||
|
}
|
||
|
|
||
|
/* Disregard any server information returned. */
|
||
|
fgets($this->_stream, 1024);
|
||
|
|
||
|
/* Send the STARTTLS command. */
|
||
|
fwrite($this->_stream, $this->_generateSid() . " STARTTLS\r\n");
|
||
|
|
||
|
/* Disregard any server information returned. */
|
||
|
fgets($this->_stream, 1024);
|
||
|
|
||
|
/* Switch over to a TLS connection. */
|
||
|
$res = stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
|
||
|
if (!$res) {
|
||
|
return PEAR::raiseError(lang("Could not open secure connection to the IMAP server. %s : %s."), 'horde.error');
|
||
|
}
|
||
|
} else {
|
||
|
$this->_stream = fsockopen($this->_host, $this->_port, $error_number, $error_string, $timeout);
|
||
|
}
|
||
|
|
||
|
/* Do some error correction */
|
||
|
if (!$this->_stream) {
|
||
|
return PEAR::raiseError(sprintf(lang("Error connecting to IMAP server. %s : %s."), $error_number, $error_string), 'horde.error');
|
||
|
}
|
||
|
|
||
|
/* Disregard any server information. */
|
||
|
fgets($this->_stream, 1024);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Log the user into the IMAP server.
|
||
|
*
|
||
|
* @param string $username Username.
|
||
|
* @param string $password Encrypted password.
|
||
|
*
|
||
|
* @return mixed True on success, PEAR_Error on error.
|
||
|
*/
|
||
|
function login($username, $password)
|
||
|
{
|
||
|
$res = $this->_createStream();
|
||
|
if (is_a($res, 'PEAR_Error')) {
|
||
|
#LK Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR);
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
$imap_auth_mech = array();
|
||
|
|
||
|
/* Use md5 authentication, if available. But no need to use special
|
||
|
* authentication if we are already using an encrypted connection. */
|
||
|
$auth_methods = $this->queryCapability('AUTH');
|
||
|
if ((!$this->_usessl || !$this->_usetls) && !empty($auth_methods)) {
|
||
|
if (in_array('CRAM-MD5', $auth_methods)) {
|
||
|
$imap_auth_mech[] = 'cram-md5';
|
||
|
}
|
||
|
if (in_array('DIGEST-MD5', $auth_methods)) {
|
||
|
$imap_auth_mech[] = 'digest-md5';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Next, try 'PLAIN' authentication. */
|
||
|
if (!empty($auth_methods) && in_array('PLAIN', $auth_methods)) {
|
||
|
$imap_auth_mech[] = 'plain';
|
||
|
}
|
||
|
|
||
|
/* Fall back to 'LOGIN' if available. */
|
||
|
if (!$this->queryCapability('LOGINDISABLED')) {
|
||
|
$imap_auth_mech[] = 'login';
|
||
|
}
|
||
|
|
||
|
if (empty($imap_auth_mech)) {
|
||
|
return PEAR::raiseError(lang("No supported IMAP authentication method could be found."), 'horde.error');
|
||
|
}
|
||
|
|
||
|
foreach ($imap_auth_mech as $method) {
|
||
|
$res = $this->_login($username, $password, $method);
|
||
|
if (!is_a($res, 'PEAR_Error')) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Log the user into the IMAP server.
|
||
|
*
|
||
|
* @access private
|
||
|
*
|
||
|
* @param string $username Username.
|
||
|
* @param string $password Encrypted password.
|
||
|
* @param string $method IMAP login method.
|
||
|
*
|
||
|
* @return mixed True on success, PEAR_Error on error.
|
||
|
*/
|
||
|
function _login($username, $password, $method)
|
||
|
{
|
||
|
switch ($method) {
|
||
|
case 'cram-md5':
|
||
|
case 'digest-md5':
|
||
|
/* If we don't have Auth_SASL package install, return error. */
|
||
|
if (!@include_once 'Auth/SASL.php') {
|
||
|
return PEAR::raiseError(lang("CRAM-MD5 or DIGEST-MD5 requires the Auth_SASL package to be installed."), 'horde.error');
|
||
|
}
|
||
|
|
||
|
$tag = $this->_generateSid();
|
||
|
fwrite($this->_stream, $tag . ' AUTHENTICATE ' . strtoupper($method) . "\r\n");
|
||
|
$challenge = explode(' ', $this->_fgets(), 3);
|
||
|
|
||
|
if ($method == 'cram-md5') {
|
||
|
$auth_sasl = Auth_SASL::factory('crammd5');
|
||
|
$response = $auth_sasl->getResponse($username, $password, base64_decode($challenge[1]));
|
||
|
fwrite($this->_stream, base64_encode($response) . "\r\n");
|
||
|
$read = $this->_fgets();
|
||
|
} elseif ($method == 'digest-md5') {
|
||
|
$auth_sasl = Auth_SASL::factory('digestmd5');
|
||
|
$response = $auth_sasl->getResponse($username, $password, base64_decode($challenge[1]), $this->_host, 'imap');
|
||
|
fwrite($this->_stream, base64_encode($response) . "\r\n");
|
||
|
$response = explode(' ', $this->_fgets());
|
||
|
$response = base64_decode($response[1]);
|
||
|
if (strpos($response, 'rspauth=') === false) {
|
||
|
return PEAR::raiseError(lang("Unexpected response from server to Digest-MD5 response."), 'horde.error');
|
||
|
}
|
||
|
fwrite($this->_stream, "\r\n");
|
||
|
$read = $this->_fgets();
|
||
|
} else {
|
||
|
return PEAR::raiseError(lang("The IMAP server does not appear to support the authentication method selected. Please contact your system administrator."), 'horde.error');
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'login':
|
||
|
$tag = $this->_generateSid();
|
||
|
$query = $tag . " LOGIN $username {" . strlen($password) . "}\r\n";
|
||
|
fwrite($this->_stream, $query);
|
||
|
$read = $this->_fgets();
|
||
|
if (substr($read, 0, 1) == '+') {
|
||
|
fwrite($this->_stream, "$password\r\n");
|
||
|
$read = $this->_fgets();
|
||
|
} else {
|
||
|
return PEAR::raiseError(lang("Unexpected response from server to LOGIN command."), 'horde.error');
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'plain':
|
||
|
$tag = $this->_generateSid();
|
||
|
$sasl = $this->queryCapability('SASL-IR');
|
||
|
$auth = base64_encode("$username\0$username\0$password");
|
||
|
if ($sasl) {
|
||
|
// IMAP Extension for SASL Initial Client Response
|
||
|
// <draft-siemborski-imap-sasl-initial-response-01b.txt>
|
||
|
$query = $tag . " AUTHENTICATE PLAIN $auth\r\n";
|
||
|
fwrite($this->_stream, $query);
|
||
|
$read = $this->_fgets();
|
||
|
} else {
|
||
|
$query = $tag . " AUTHENTICATE PLAIN\r\n";
|
||
|
fwrite($this->_stream, $query);
|
||
|
$read = $this->_fgets();
|
||
|
if (substr($read, 0, 1) == '+') {
|
||
|
fwrite($this->_stream, "$auth\r\n");
|
||
|
$read = $this->_fgets();
|
||
|
} else {
|
||
|
return PEAR::raiseError(lang("Unexpected response from server to AUTHENTICATE command."), 'horde.error');
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Check for failed login. */
|
||
|
$results = explode(' ', $read, 3);
|
||
|
$response = $results[1];
|
||
|
|
||
|
if ($response != 'OK') {
|
||
|
$message = !empty($results[2]) ? htmlspecialchars($results[2]) : lang("No message returned.");
|
||
|
|
||
|
switch ($response) {
|
||
|
case 'NO':
|
||
|
return PEAR::raiseError(sprintf(lang("Bad login name or password."), $message), 'horde.error');
|
||
|
|
||
|
case 'BAD':
|
||
|
default:
|
||
|
return PEAR::raiseError(sprintf(lang("Bad request: %s"), $message), 'horde.error');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Log out of the IMAP session.
|
||
|
*/
|
||
|
function logout()
|
||
|
{
|
||
|
/* Logout is not valid until the server returns 'BYE'
|
||
|
* If we don't have an imap_ stream we're already logged out */
|
||
|
if (isset($this->_stream) && $this->_stream) {
|
||
|
$this->_runCommand('LOGOUT');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the CAPABILITY string from the IMAP server.
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
function _capability()
|
||
|
{
|
||
|
if (!is_null($this->_capability)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$this->_capability = array();
|
||
|
$read = $this->_runCommand('CAPABILITY');
|
||
|
if (is_a($read, 'PEAR_Error')) {
|
||
|
#LK Horde::logMessage($read, __FILE__, __LINE__, PEAR_LOG_ERR);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$c = explode(' ', trim($read[0]));
|
||
|
for ($i = 2; $i < count($c); $i++) {
|
||
|
$cap_list = explode('=', $c[$i]);
|
||
|
if (isset($cap_list[1])) {
|
||
|
if (!isset($this->_capability[$cap_list[0]])) {
|
||
|
$this->_capability[$cap_list[0]] = array();
|
||
|
}
|
||
|
$this->_capability[$cap_list[0]][] = $cap_list[1];
|
||
|
} else {
|
||
|
$this->_capability[$cap_list[0]] = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns whether the IMAP server supports the given capability.
|
||
|
*
|
||
|
* @param string $capability The capability string to query.
|
||
|
*
|
||
|
* @param mixed True if the server supports the queried capability,
|
||
|
* false if it doesn't, or an array if the capability can
|
||
|
* contain multiple values.
|
||
|
*/
|
||
|
function queryCapability($capability)
|
||
|
{
|
||
|
$this->_capability();
|
||
|
return isset($this->_capability[$capability]) ? $this->_capability[$capability] : false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the NAMESPACE information from the IMAP server.
|
||
|
*
|
||
|
* @param array $additional If the server supports namespaces, any
|
||
|
* additional namespaces to add to the
|
||
|
* namespace list that are not broadcast by
|
||
|
* the server.
|
||
|
*
|
||
|
* @return array An array with the following format:
|
||
|
* <pre>
|
||
|
* Array
|
||
|
* (
|
||
|
* [foo] => Array
|
||
|
* (
|
||
|
* [name] => (string)
|
||
|
* [delimiter] => (string)
|
||
|
* [type] => 'personal' | 'others' | 'shared'
|
||
|
* [hidden] => (boolean)
|
||
|
* )
|
||
|
*
|
||
|
* [foo2] => Array
|
||
|
* (
|
||
|
* ...
|
||
|
* )
|
||
|
* )
|
||
|
* </pre>
|
||
|
*/
|
||
|
function namespace($additional = array())
|
||
|
{
|
||
|
if (!is_null($this->_namespace)) {
|
||
|
return $this->_namespace;
|
||
|
}
|
||
|
|
||
|
$namespace_array = array(
|
||
|
1 => 'personal',
|
||
|
2 => 'others',
|
||
|
3 => 'shared'
|
||
|
);
|
||
|
|
||
|
if ($this->queryCapability('NAMESPACE')) {
|
||
|
/*
|
||
|
* According to rfc2342 response from NAMESPACE command is:
|
||
|
* * NAMESPACE (PERSONAL NAMESPACES) (OTHER_USERS NAMESPACE) (SHARED NAMESPACES)
|
||
|
*/
|
||
|
$read = $this->_runCommand('NAMESPACE');
|
||
|
if (is_a($read, 'PEAR_Error')) {
|
||
|
#LK Horde::logMessage($read, __FILE__, __LINE__, PEAR_LOG_ERR);
|
||
|
return $read;
|
||
|
}
|
||
|
|
||
|
if (preg_match('/\\* NAMESPACE +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL) +(\\( *\\(.+\\) *\\)|NIL)/i', $read[0], $data)) {
|
||
|
for ($i = 1; $i <= 3; $i++) {
|
||
|
if ($data[$i] == 'NIL') {
|
||
|
continue;
|
||
|
}
|
||
|
$pna = explode(')(', $data[$i]);
|
||
|
while (list($k, $v) = each($pna)) {
|
||
|
$lst = explode('"', $v);
|
||
|
$delimiter = (isset($lst[3])) ? $lst[3] : '';
|
||
|
$this->_namespace[$lst[1]] = array('name' => $lst[1], 'delimiter' => $delimiter, 'type' => $namespace_array[$i], 'hidden' => false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach ($additional as $val) {
|
||
|
/* Skip namespaces if we have already auto-detected them.
|
||
|
* Also, hidden namespaces cannot be empty. */
|
||
|
$val = trim($val);
|
||
|
if (empty($val) || isset($this->_namespace[$val])) {
|
||
|
continue;
|
||
|
}
|
||
|
$read = $this->_runCommand('LIST "" "' . $val . '"');
|
||
|
if (is_a($read, 'PEAR_Error')) {
|
||
|
#LK Horde::logMessage($read, __FILE__, __LINE__, PEAR_LOG_ERR);
|
||
|
return $res;
|
||
|
}
|
||
|
if (!empty($read) &&
|
||
|
preg_match("/^\* LIST \(.*\) \"(.*)\" \"?(.*?)\"?\s*$/", $read[0], $data) &&
|
||
|
($data[2] == $val)) {
|
||
|
$this->_namespace[$val] = array('name' => $val, 'delimiter' => $data[1], 'type' => $namespace_array[3], 'hidden' => true);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
$res = $this->_runCommand('LIST "" ""');
|
||
|
if (is_a($res, 'PEAR_Error')) {
|
||
|
#LK Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR);
|
||
|
return $res;
|
||
|
}
|
||
|
$quote_position = strpos($res[0], '"');
|
||
|
$this->_namespace[''] = array('name' => '', 'delimiter' => substr($res[0], $quote_position + 1 , 1), 'type' => $namespace_array[1], 'hidden' => false);
|
||
|
}
|
||
|
|
||
|
return $this->_namespace;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines whether the IMAP search command supports the optional
|
||
|
* charset provided.
|
||
|
*
|
||
|
* @param string $charset The character set to test.
|
||
|
*
|
||
|
* @return boolean True if the IMAP search command supports the charset.
|
||
|
*/
|
||
|
function searchCharset($charset)
|
||
|
{
|
||
|
$this->_runCommand('SELECT INBOX');
|
||
|
$read = $this->_runCommand('SEARCH CHARSET ' . $charset . ' TEXT "charsettest" 1');
|
||
|
return !is_a($read, 'PEAR_Error');
|
||
|
}
|
||
|
|
||
|
}
|