forked from extern/egroupware
536 lines
16 KiB
PHP
536 lines
16 KiB
PHP
<?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;
|
|
}
|
|
|
|
if(PEAR::isError($this->error = $this->connect($sieveHost , $sievePort, $options, $useTLS) ) )
|
|
{
|
|
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];
|
|
}
|
|
|
|
if(PEAR::isError($this->error = $this->login($username, $password, 'LOGIN', $euser) ) )
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (PEAR::isError($res = $this->_sock->connect($host, $port, false, ($this->_timeout?$this->_timeout:10), $options))) {
|
|
return $res;
|
|
}
|
|
|
|
if ($this->_bypassAuth) {
|
|
$this->_state = NET_SIEVE_STATE_TRANSACTION;
|
|
} else {
|
|
$this->_state = NET_SIEVE_STATE_AUTHORISATION;
|
|
if (PEAR::isError($res = $this->_doCmd())) {
|
|
return $res;
|
|
}
|
|
}
|
|
|
|
// Explicitly ask for the capabilities in case the connection is
|
|
// picked up from an existing connection.
|
|
if (PEAR::isError($res = $this->_cmdCapability())) {
|
|
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')
|
|
) {
|
|
if (PEAR::isError($res = $this->_startTLS())) {
|
|
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 = '' )
|
|
{
|
|
if ( PEAR::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
|
|
return $method;
|
|
}
|
|
//error_log(__METHOD__.__LINE__.' using AuthMethod: '.$method);
|
|
switch ($method) {
|
|
case 'DIGEST-MD5':
|
|
$result = $this->_authDigestMD5( $uid , $pwd , $euser );
|
|
if (!PEAR::isError($result))
|
|
{
|
|
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);
|
|
if (!PEAR::isError($result))
|
|
{
|
|
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 );
|
|
if (!PEAR::isError($result))
|
|
{
|
|
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;
|
|
}
|
|
if (PEAR::isError($result))
|
|
{
|
|
return $result;
|
|
}
|
|
if (PEAR::isError($res = $this->_doCmd())) {
|
|
return $res;
|
|
}
|
|
|
|
// Query the server capabilities again now that we are authenticated.
|
|
if (PEAR::isError($res = $this->_cmdCapability())) {
|
|
return PEAR::raiseError(
|
|
'Failed to connect, server said: ' . $res->getMessage(), 2
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
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
|
|
*/
|
|
function setRules(array $_rules, $_scriptName=null)
|
|
{
|
|
$script = $this->retrieveRules($_scriptName);
|
|
$script->debug = $this->debug;
|
|
$script->rules = $_rules;
|
|
$ret = $script->updateScript($this);
|
|
$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;
|
|
}
|
|
}
|