2015-01-21 20:49:09 +01:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* EGroupware EMailAdmin: Support for Sieve scripts
|
|
|
|
*
|
|
|
|
* @link http://www.egroupware.org
|
|
|
|
* @package emailadmin
|
|
|
|
* @author Ralf Becker <rb@stylite.de>
|
|
|
|
* @author Klaus Leithoff <kl@stylite.de>
|
|
|
|
* @author Lars Kneschke
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @version $Id$
|
|
|
|
*/
|
|
|
|
|
|
|
|
include_once('Net/Sieve.php');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Support for Sieve scripts
|
|
|
|
*
|
|
|
|
* Class can be switched to use exceptions by calling
|
|
|
|
*
|
|
|
|
* PEAR::setErrorHandling(PEAR_ERROR_EXCEPTION);
|
|
|
|
*
|
|
|
|
* In which case constructor and setters will throw exceptions for connection, login or other errors.
|
|
|
|
*
|
|
|
|
* retriveRules and getters will not throw an exception, if there's no script currently.
|
|
|
|
*
|
|
|
|
* Most methods incl. constructor accept a script-name, but by default current active script is used
|
|
|
|
* and if theres no script emailadmin_sieve::DEFAULT_SCRIPT_NAME.
|
|
|
|
*/
|
|
|
|
class emailadmin_sieve extends Net_Sieve
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* reference to emailadmin_imap object
|
|
|
|
*
|
|
|
|
* @var emailadmin_imap
|
|
|
|
*/
|
|
|
|
var $icServer;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string name of active script queried from Sieve server
|
|
|
|
*/
|
|
|
|
var $scriptName;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var $rules containing the rules
|
|
|
|
*/
|
|
|
|
var $rules;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var $vacation containing the vacation
|
|
|
|
*/
|
|
|
|
var $vacation;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var $emailNotification containing the emailNotification
|
|
|
|
*/
|
|
|
|
var $emailNotification;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var object $error the last PEAR error object
|
|
|
|
*/
|
|
|
|
var $error;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The timeout for the connection to the SIEVE server.
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
var $_timeout = 10;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Switch on some error_log debug messages
|
|
|
|
*
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
var $debug = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default script name used if no active script found on server
|
|
|
|
*/
|
|
|
|
const DEFAULT_SCRIPT_NAME = 'mail';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
* @param emailadmin_imap $_icServer
|
|
|
|
* @param string $_euser effictive user, if given the Cyrus admin account is used to login on behalf of $euser
|
|
|
|
* @param string $_scriptName
|
|
|
|
*/
|
|
|
|
function __construct(emailadmin_imap $_icServer=null, $_euser='', $_scriptName=null)
|
|
|
|
{
|
|
|
|
parent::Net_Sieve();
|
|
|
|
|
|
|
|
if ($_scriptName) $this->scriptName = $_scriptName;
|
|
|
|
|
|
|
|
// TODO: since we seem to have major problems authenticating via DIGEST-MD5 and CRAM-MD5 in SIEVE, we skip MD5-METHODS for now
|
|
|
|
if (!is_null($_icServer))
|
|
|
|
{
|
|
|
|
$_icServer->supportedAuthMethods = array('PLAIN' , 'LOGIN');
|
|
|
|
$_icServer->supportedSASLAuthMethods=array();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$this->supportedAuthMethods = array('PLAIN' , 'LOGIN');
|
|
|
|
$this->supportedSASLAuthMethods=array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->displayCharset = translation::charset();
|
|
|
|
|
|
|
|
if (!is_null($_icServer) && $this->_connect($_icServer, $_euser) === 'die') {
|
|
|
|
die('Sieve not activated');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open connection to the sieve server
|
|
|
|
*
|
|
|
|
* @param emailadmin_imap $_icServer
|
|
|
|
* @param string $euser effictive user, if given the Cyrus admin account is used to login on behalf of $euser
|
|
|
|
* @return mixed 'die' = sieve not enabled, false=connect or login failure, true=success
|
|
|
|
*/
|
|
|
|
function _connect(emailadmin_imap $_icServer, $euser='')
|
|
|
|
{
|
|
|
|
static $isConError = null;
|
|
|
|
static $sieveAuthMethods = null;
|
|
|
|
$_icServerID = $_icServer->acc_id;
|
|
|
|
if (is_null($isConError))
|
|
|
|
{
|
|
|
|
$isConError = egw_cache::getCache(egw_cache::INSTANCE, 'email', 'icServerSIEVE_connectionError' . trim($GLOBALS['egw_info']['user']['account_id']));
|
|
|
|
}
|
|
|
|
if ( isset($isConError[$_icServerID]) )
|
|
|
|
{
|
|
|
|
$this->error = new PEAR_Error($isConError[$_icServerID]);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->debug)
|
|
|
|
{
|
|
|
|
error_log(__METHOD__ . array2string($euser));
|
|
|
|
}
|
|
|
|
if($_icServer->acc_sieve_enabled)
|
|
|
|
{
|
|
|
|
if ($_icServer->acc_sieve_host)
|
|
|
|
{
|
|
|
|
$sieveHost = $_icServer->acc_sieve_host;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$sieveHost = $_icServer->acc_imap_host;
|
|
|
|
}
|
|
|
|
//error_log(__METHOD__.__LINE__.'->'.$sieveHost);
|
|
|
|
$sievePort = $_icServer->acc_sieve_port;
|
|
|
|
|
|
|
|
$useTLS = false;
|
|
|
|
|
|
|
|
switch($_icServer->acc_sieve_ssl & ~emailadmin_account::SSL_VERIFY)
|
|
|
|
{
|
|
|
|
case emailadmin_account::SSL_SSL:
|
|
|
|
$sieveHost = 'ssl://'.$sieveHost;
|
|
|
|
break;
|
|
|
|
case emailadmin_account::SSL_TLS:
|
|
|
|
$sieveHost = 'tls://'.$sieveHost;
|
|
|
|
break;
|
|
|
|
case emailadmin_account::SSL_STARTTLS:
|
|
|
|
$useTLS = true;
|
|
|
|
}
|
|
|
|
// disable certificate validation, if not explicitly enabled (not possible in current UI, as not supported by Horde_Imap_Client)
|
|
|
|
$options = array(
|
|
|
|
'ssl' => array(
|
|
|
|
'verify_peer' => (bool)($_icServer->acc_sieve_ssl & emailadmin_account::SSL_VERIFY),
|
|
|
|
'verify_peer_name' => (bool)($_icServer->acc_sieve_ssl & emailadmin_account::SSL_VERIFY),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($euser)
|
|
|
|
{
|
|
|
|
$username = $_icServer->acc_imap_admin_username;
|
|
|
|
$password = $_icServer->acc_imap_admin_password;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$username = $_icServer->acc_imap_username;
|
|
|
|
$password = $_icServer->acc_imap_password;
|
|
|
|
}
|
|
|
|
$this->icServer = $_icServer;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
egw_cache::setCache(egw_cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),$isConError,$expiration=60*15);
|
|
|
|
return 'die';
|
|
|
|
}
|
|
|
|
$this->_timeout = 10; // socket::connect sets the/this timeout on connection
|
|
|
|
$timeout = emailadmin_imap::getTimeOut('SIEVE');
|
|
|
|
if ($timeout > $this->_timeout)
|
|
|
|
{
|
|
|
|
$this->_timeout = $timeout;
|
|
|
|
}
|
|
|
|
|
2015-01-26 17:12:10 +01:00
|
|
|
if(self::isError($this->error = $this->connect($sieveHost , $sievePort, $options, $useTLS) ) )
|
2015-01-21 20:49:09 +01:00
|
|
|
{
|
|
|
|
if ($this->debug)
|
|
|
|
{
|
|
|
|
error_log(__METHOD__ . ": error in connect($sieveHost,$sievePort, " . array2string($options) . ", $useTLS): " . $this->error->getMessage());
|
|
|
|
}
|
|
|
|
$isConError[$_icServerID] = $this->error->getMessage();
|
|
|
|
egw_cache::setCache(egw_cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),$isConError,$expiration=60*15);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// we cache the supported AuthMethods during session, to be able to speed up login.
|
|
|
|
if (is_null($sieveAuthMethods))
|
|
|
|
{
|
|
|
|
$sieveAuthMethods = & egw_cache::getSession('email', 'sieve_supportedAuthMethods');
|
|
|
|
}
|
|
|
|
if (isset($sieveAuthMethods[$_icServerID]))
|
|
|
|
{
|
|
|
|
$this->supportedAuthMethods = $sieveAuthMethods[$_icServerID];
|
|
|
|
}
|
|
|
|
|
2015-01-26 17:12:10 +01:00
|
|
|
if(self::isError($this->error = $this->login($username, $password, 'LOGIN', $euser) ) )
|
2015-01-21 20:49:09 +01:00
|
|
|
{
|
|
|
|
if ($this->debug)
|
|
|
|
{
|
|
|
|
error_log(__METHOD__ . ": error in login($username,$password,null,$euser): " . $this->error->getMessage());
|
|
|
|
}
|
|
|
|
$isConError[$_icServerID] = $this->error->getMessage();
|
|
|
|
egw_cache::setCache(egw_cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),$isConError,$expiration=60*15);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// query active script from Sieve server
|
|
|
|
if (empty($this->scriptName))
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$this->scriptName = $this->getActive();
|
|
|
|
}
|
|
|
|
catch(Exception $e) {
|
|
|
|
unset($e); // ignore NOTEXISTS exception
|
|
|
|
}
|
|
|
|
if (empty($this->scriptName))
|
|
|
|
{
|
|
|
|
$this->scriptName = self::DEFAULT_SCRIPT_NAME;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//error_log(__METHOD__.__LINE__.array2string($this->_capability));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles connecting to the server and checks the response validity.
|
|
|
|
* overwritten function from Net_Sieve to respect timeout
|
|
|
|
*
|
|
|
|
* @param string $host Hostname of server.
|
|
|
|
* @param string $port Port of server.
|
|
|
|
* @param array $options List of options to pass to
|
|
|
|
* stream_context_create().
|
|
|
|
* @param boolean $useTLS Use TLS if available.
|
|
|
|
*
|
|
|
|
* @return boolean True on success, PEAR_Error otherwise.
|
|
|
|
*/
|
|
|
|
function connect($host, $port, $options = null, $useTLS = true)
|
|
|
|
{
|
|
|
|
if ($this->debug)
|
|
|
|
{
|
|
|
|
error_log(__METHOD__ . __LINE__ . "$host, $port, " . array2string($options) . ", $useTLS");
|
|
|
|
}
|
|
|
|
$this->_data['host'] = $host;
|
|
|
|
$this->_data['port'] = $port;
|
|
|
|
$this->_useTLS = $useTLS;
|
|
|
|
if (is_array($options)) {
|
|
|
|
$this->_options = array_merge((array)$this->_options, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
|
|
|
|
return PEAR::raiseError('Not currently in DISCONNECTED state', 1);
|
|
|
|
}
|
|
|
|
|
2015-01-26 17:12:10 +01:00
|
|
|
if (self::isError($res = $this->_sock->connect($host, $port, false, ($this->_timeout?$this->_timeout:10), $options))) {
|
2015-01-21 20:49:09 +01:00
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->_bypassAuth) {
|
|
|
|
$this->_state = NET_SIEVE_STATE_TRANSACTION;
|
|
|
|
} else {
|
|
|
|
$this->_state = NET_SIEVE_STATE_AUTHORISATION;
|
2015-01-26 17:12:10 +01:00
|
|
|
if (self::isError($res = $this->_doCmd())) {
|
2015-01-21 20:49:09 +01:00
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Explicitly ask for the capabilities in case the connection is
|
|
|
|
// picked up from an existing connection.
|
2015-01-26 17:12:10 +01:00
|
|
|
if (self::isError($res = $this->_cmdCapability())) {
|
2015-01-21 20:49:09 +01:00
|
|
|
return PEAR::raiseError(
|
|
|
|
'Failed to connect, server said: ' . $res->getMessage(), 2
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we can enable TLS via STARTTLS.
|
|
|
|
if ($useTLS && !empty($this->_capability['starttls'])
|
|
|
|
&& function_exists('stream_socket_enable_crypto')
|
|
|
|
) {
|
2015-01-26 17:12:10 +01:00
|
|
|
if (self::isError($res = $this->_startTLS())) {
|
2015-01-21 20:49:09 +01:00
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Own _getBestAuthMethod as Net/Sieve.php assumes SASLMethods to be working
|
|
|
|
* Returns the name of the best authentication method that the server
|
|
|
|
* has advertised.
|
|
|
|
*
|
|
|
|
* @param string if !=null,check this one first if reported as serverMethod.
|
|
|
|
* if so, return as bestauthmethod
|
|
|
|
* @return mixed Returns a string containing the name of the best
|
|
|
|
* supported authentication method or a PEAR_Error object
|
|
|
|
* if a failure condition is encountered.
|
|
|
|
*/
|
|
|
|
function _getBestAuthMethod($userMethod = null)
|
|
|
|
{
|
|
|
|
//error_log(__METHOD__.__LINE__.'->'.$userMethod.'<->'.array2string($this->_capability['sasl']));
|
|
|
|
if( isset($this->_capability['sasl']) ){
|
|
|
|
$serverMethods=$this->_capability['sasl'];
|
|
|
|
}else{
|
|
|
|
// if the server don't send an sasl capability fallback to login auth
|
|
|
|
//return 'LOGIN';
|
|
|
|
return PEAR::raiseError("This server don't support any Auth methods SASL problem?");
|
|
|
|
}
|
|
|
|
$methods = array();
|
|
|
|
if($userMethod != null ){
|
|
|
|
$methods[] = $userMethod;
|
|
|
|
foreach ( $this->supportedAuthMethods as $method ) {
|
|
|
|
$methods[]=$method;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
$methods = $this->supportedAuthMethods;
|
|
|
|
}
|
|
|
|
if( ($methods != null) && ($serverMethods != null)){
|
|
|
|
foreach ( $methods as $method ) {
|
|
|
|
if ( in_array( $method , $serverMethods ) ) {
|
|
|
|
return $method;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$serverMethods=implode(',' , $serverMethods );
|
|
|
|
$myMethods=implode(',' ,$this->supportedAuthMethods);
|
|
|
|
return PEAR::raiseError("$method NOT supported authentication method!. This server " .
|
|
|
|
"supports these methods= $serverMethods, but I support $myMethods");
|
|
|
|
}else{
|
|
|
|
return PEAR::raiseError("This server don't support any Auth methods");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles the authentication using any known method
|
|
|
|
* overwritten function from Net_Sieve to support fallback
|
|
|
|
*
|
|
|
|
* @param string $uid The userid to authenticate as.
|
|
|
|
* @param string $pwd The password to authenticate with.
|
|
|
|
* @param string $userMethod The method to use ( if $userMethod == '' then the class chooses the best method (the stronger is the best ) )
|
|
|
|
* @param string $euser The effective uid to authenticate as.
|
|
|
|
*
|
|
|
|
* @return mixed string or PEAR_Error
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @since 1.0
|
|
|
|
*/
|
|
|
|
function _cmdAuthenticate($uid , $pwd , $userMethod = null , $euser = '' )
|
|
|
|
{
|
2015-01-26 17:12:10 +01:00
|
|
|
if ( self::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
|
2015-01-21 20:49:09 +01:00
|
|
|
return $method;
|
|
|
|
}
|
|
|
|
//error_log(__METHOD__.__LINE__.' using AuthMethod: '.$method);
|
|
|
|
switch ($method) {
|
|
|
|
case 'DIGEST-MD5':
|
|
|
|
$result = $this->_authDigestMD5( $uid , $pwd , $euser );
|
2015-01-26 17:12:10 +01:00
|
|
|
if (!self::isError($result))
|
2015-01-21 20:49:09 +01:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$res = $this->_doCmd();
|
|
|
|
unset($this->_error);
|
|
|
|
$this->supportedAuthMethods = array_diff($this->supportedAuthMethods,array($method,'CRAM-MD5'));
|
|
|
|
return $this->_cmdAuthenticate($uid , $pwd, null, $euser);
|
|
|
|
case 'CRAM-MD5':
|
|
|
|
$result = $this->_authCRAMMD5( $uid , $pwd, $euser);
|
2015-01-26 17:12:10 +01:00
|
|
|
if (!self::isError($result))
|
2015-01-21 20:49:09 +01:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$res = $this->_doCmd();
|
|
|
|
unset($this->_error);
|
|
|
|
$this->supportedAuthMethods = array_diff($this->supportedAuthMethods,array($method,'DIGEST-MD5'));
|
|
|
|
return $this->_cmdAuthenticate($uid , $pwd, null, $euser);
|
|
|
|
case 'LOGIN':
|
|
|
|
$result = $this->_authLOGIN( $uid , $pwd , $euser );
|
2015-01-26 17:12:10 +01:00
|
|
|
if (!self::isError($result))
|
2015-01-21 20:49:09 +01:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$res = $this->_doCmd();
|
|
|
|
unset($this->_error);
|
|
|
|
$this->supportedAuthMethods = array_diff($this->supportedAuthMethods,array($method));
|
|
|
|
return $this->_cmdAuthenticate($uid , $pwd, null, $euser);
|
|
|
|
case 'PLAIN':
|
|
|
|
$result = $this->_authPLAIN( $uid , $pwd , $euser );
|
|
|
|
break;
|
|
|
|
default :
|
|
|
|
$result = new PEAR_Error( "$method is not a supported authentication method" );
|
|
|
|
break;
|
|
|
|
}
|
2015-01-26 17:12:10 +01:00
|
|
|
if (self::isError($result))
|
2015-01-21 20:49:09 +01:00
|
|
|
{
|
|
|
|
return $result;
|
|
|
|
}
|
2015-01-26 17:12:10 +01:00
|
|
|
if (self::isError($res = $this->_doCmd())) {
|
2015-01-21 20:49:09 +01:00
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query the server capabilities again now that we are authenticated.
|
2015-01-26 17:12:10 +01:00
|
|
|
if (self::isError($res = $this->_cmdCapability())) {
|
2015-01-21 20:49:09 +01:00
|
|
|
return PEAR::raiseError(
|
|
|
|
'Failed to connect, server said: ' . $res->getMessage(), 2
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2015-01-27 13:23:10 +01:00
|
|
|
/**
|
|
|
|
* Send a command and retrieves a response from the server.
|
|
|
|
*
|
|
|
|
* @param string $cmd The command to send.
|
|
|
|
* @param boolean $auth Whether this is an authentication command.
|
|
|
|
*
|
|
|
|
* @return string|PEAR_Error Reponse string if an OK response, PEAR_Error
|
|
|
|
* if a NO response.
|
|
|
|
*/
|
|
|
|
function _doCmd($cmd = '', $auth = false)
|
|
|
|
{
|
|
|
|
$referralCount = 0;
|
|
|
|
while ($referralCount < $this->_maxReferralCount) {
|
|
|
|
if (strlen($cmd)) {
|
|
|
|
$error = $this->_sendCmd($cmd);
|
|
|
|
if (is_a($error, 'PEAR_Error')) {
|
|
|
|
return $error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$response = '';
|
|
|
|
while (true) {
|
|
|
|
$line = $this->_recvLn();
|
|
|
|
|
|
|
|
if (is_a($line, 'PEAR_Error')) {
|
|
|
|
return $line;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preg_match('/^(OK|NO)/i', $line, $tag)) {
|
|
|
|
// Check for string literal message.
|
|
|
|
// ServerResponse may send {nm} (nm representing a number)
|
|
|
|
// dbmail (in some versions) sends: {nm+} thus breaking RFC5804 rules (Section 4 Formal Syntax)
|
|
|
|
// {nm+} may only be used in communicating from client TO server; (not Server to Client)
|
|
|
|
// we work around this bug (allowing +) using a patch introduced with roundcube 2 years ago.
|
|
|
|
//if (preg_match('/{([0-9]+)}$/', $line, $matches)) { //original
|
|
|
|
if (preg_match('/{([0-9]+)\+?}$/', $line, $matches)) { //patched to cope with dbmail
|
|
|
|
$line = substr($line, 0, -(strlen($matches[1]) + 2))
|
|
|
|
. str_replace(
|
|
|
|
"\r\n", ' ', $this->_recvBytes($matches[1] + 2)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ('OK' == $this->_toUpper($tag[1])) {
|
|
|
|
$response .= $line;
|
|
|
|
return rtrim($response);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->_pear->raiseError(trim($response . substr($line, 2)), 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preg_match('/^BYE/i', $line)) {
|
|
|
|
$error = $this->disconnect(false);
|
|
|
|
if (is_a($error, 'PEAR_Error')) {
|
|
|
|
return $this->_pear->raiseError(
|
|
|
|
'Cannot handle BYE, the error was: '
|
|
|
|
. $error->getMessage(),
|
|
|
|
4
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// Check for referral, then follow it. Otherwise, carp an
|
|
|
|
// error.
|
|
|
|
if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
|
|
|
|
// Replace the old host with the referral host
|
|
|
|
// preserving any protocol prefix.
|
|
|
|
$this->_data['host'] = preg_replace(
|
|
|
|
'/\w+(?!(\w|\:\/\/)).*/', $matches[2],
|
|
|
|
$this->_data['host']
|
|
|
|
);
|
|
|
|
$error = $this->_handleConnectAndLogin();
|
|
|
|
if (is_a($error, 'PEAR_Error')) {
|
|
|
|
return $this->_pear->raiseError(
|
|
|
|
'Cannot follow referral to '
|
|
|
|
. $this->_data['host'] . ', the error was: '
|
|
|
|
. $error->getMessage(),
|
|
|
|
5
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return $this->_pear->raiseError(trim($response . $line), 6);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServerResponse may send {nm} (nm representing a number)
|
|
|
|
// dbmail (in some versions) sends: {nm+} thus breaking RFC5804 rules (Section 4 Formal Syntax)
|
|
|
|
// {nm+} may only be used in communicating from client TO server; (not Server to Client)
|
|
|
|
// we work around this bug (allowing +) using a patch introduced with roundcube 2 years ago.
|
|
|
|
// although roundcube suggested only the change in line
|
|
|
|
//if (preg_match('/^{([0-9]+)}/', $line, $matches)) { //original
|
|
|
|
if (preg_match('/^{([0-9]+)\+?}/', $line, $matches)) { //patched to cope with dbmail
|
|
|
|
// Matches literal string responses.
|
|
|
|
$line = $this->_recvBytes($matches[1] + 2);
|
|
|
|
if (!$auth) {
|
|
|
|
// Receive the pending OK only if we aren't
|
|
|
|
// authenticating since string responses during
|
|
|
|
// authentication don't need an OK.
|
|
|
|
$this->_recvLn();
|
|
|
|
}
|
|
|
|
return $line;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($auth) {
|
|
|
|
// String responses during authentication don't need an
|
|
|
|
// OK.
|
|
|
|
$response .= $line;
|
|
|
|
return rtrim($response);
|
|
|
|
}
|
|
|
|
|
|
|
|
$response .= $line . "\r\n";
|
|
|
|
$referralCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->_pear->raiseError('Max referral count (' . $referralCount . ') reached. Cyrus murder loop error?', 7);
|
|
|
|
}
|
|
|
|
|
2015-01-21 20:49:09 +01:00
|
|
|
function getRules()
|
|
|
|
{
|
|
|
|
if (!isset($this->rules)) $this->retrieveRules();
|
|
|
|
|
|
|
|
return $this->rules;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVacation()
|
|
|
|
{
|
|
|
|
if (!isset($this->rules)) $this->retrieveRules();
|
|
|
|
|
|
|
|
return $this->vacation;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getEmailNotification()
|
|
|
|
{
|
|
|
|
if (!isset($this->rules)) $this->retrieveRules();
|
|
|
|
|
|
|
|
return $this->emailNotification;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set email notifications
|
|
|
|
*
|
|
|
|
* @param array $_rules
|
|
|
|
* @param string $_scriptName
|
2015-06-22 20:49:44 +02:00
|
|
|
* @param boolean $utf7imap_fileinto =false true: encode foldernames with utf7imap, default utf8
|
2015-01-21 20:49:09 +01:00
|
|
|
*/
|
2015-06-22 20:49:44 +02:00
|
|
|
function setRules(array $_rules, $_scriptName=null, $utf7imap_fileinto=false)
|
2015-01-21 20:49:09 +01:00
|
|
|
{
|
|
|
|
$script = $this->retrieveRules($_scriptName);
|
|
|
|
$script->debug = $this->debug;
|
|
|
|
$script->rules = $_rules;
|
2015-06-22 20:49:44 +02:00
|
|
|
$ret = $script->updateScript($this, $utf7imap_fileinto);
|
2015-01-21 20:49:09 +01:00
|
|
|
$this->error = $script->errstr;
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set email notifications
|
|
|
|
*
|
|
|
|
* @param array $_vacation
|
|
|
|
* @param string $_scriptName
|
|
|
|
*/
|
|
|
|
function setVacation(array $_vacation, $_scriptName=null)
|
|
|
|
{
|
|
|
|
if ($this->debug)
|
|
|
|
{
|
|
|
|
error_log(__METHOD__ . "($_scriptName," . print_r($_vacation, true) . ')');
|
|
|
|
}
|
|
|
|
$script = $this->retrieveRules($_scriptName);
|
|
|
|
$script->debug = $this->debug;
|
|
|
|
$script->vacation = $_vacation;
|
|
|
|
$ret = $script->updateScript($this);
|
|
|
|
$this->error = $script->errstr;
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set email notifications
|
|
|
|
*
|
|
|
|
* @param array $_emailNotification
|
|
|
|
* @param string $_scriptName
|
|
|
|
* @return emailadmin_script
|
|
|
|
*/
|
|
|
|
function setEmailNotification(array $_emailNotification, $_scriptName=null)
|
|
|
|
{
|
|
|
|
if ($_emailNotification['externalEmail'] == '' || !preg_match("/\@/",$_emailNotification['externalEmail'])) {
|
|
|
|
$_emailNotification['status'] = 'off';
|
|
|
|
$_emailNotification['externalEmail'] = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
$script = $this->retrieveRules($_scriptName);
|
|
|
|
$script->emailNotification = $_emailNotification;
|
|
|
|
$ret = $script->updateScript($this);
|
|
|
|
$this->error = $script->errstr;
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrive rules, vacation, notifications and return emailadmin_script object to update them
|
|
|
|
*
|
|
|
|
* @param string $_scriptName
|
|
|
|
* @return emailadmin_script
|
|
|
|
*/
|
|
|
|
function retrieveRules($_scriptName=null)
|
|
|
|
{
|
|
|
|
if (!$_scriptName)
|
|
|
|
{
|
|
|
|
$_scriptName = $this->scriptName;
|
|
|
|
}
|
|
|
|
$script = new emailadmin_script($_scriptName);
|
|
|
|
|
|
|
|
try {
|
|
|
|
$script->retrieveRules($this);
|
|
|
|
}
|
|
|
|
catch (Exception $e) {
|
|
|
|
unset($e); // ignore not found script exception
|
|
|
|
}
|
|
|
|
$this->rules =& $script->rules;
|
|
|
|
$this->vacation =& $script->vacation;
|
|
|
|
$this->emailNotification =& $script->emailNotification; // Added email notifications
|
|
|
|
|
|
|
|
return $script;
|
|
|
|
}
|
2015-01-26 17:12:10 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tell whether a value is a PEAR error.
|
|
|
|
*
|
|
|
|
* Implemented here to get arround: PHP Deprecated: Non-static method self::isError() should not be called statically
|
|
|
|
*
|
|
|
|
* @param mixed $data the value to test
|
|
|
|
* @param int $code if $data is an error object, return true
|
|
|
|
* only if $code is a string and
|
|
|
|
* $obj->getMessage() == $code or
|
|
|
|
* $code is an integer and $obj->getCode() == $code
|
|
|
|
* @access public
|
|
|
|
* @return bool true if parameter is an error
|
|
|
|
*/
|
|
|
|
protected static function isError($data, $code = null)
|
|
|
|
{
|
|
|
|
static $pear=null;
|
|
|
|
if (!isset($pear)) $pear = new PEAR();
|
|
|
|
|
|
|
|
return $pear->isError($data, $code);
|
|
|
|
}
|
2015-01-21 20:49:09 +01:00
|
|
|
}
|