From: Jan Schneider Date: Mon, 23 Aug 2010 14:09:15 +0000 (+0200) Subject: Driver -> Transport X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=6db502748dbb4452496d556d30396a0767ef50ab;p=horde.git Driver -> Transport --- diff --git a/ingo/config/backends.php.dist b/ingo/config/backends.php.dist index 4306b9272..873b7365e 100644 --- a/ingo/config/backends.php.dist +++ b/ingo/config/backends.php.dist @@ -3,12 +3,12 @@ * Ingo works purely on a preferred mechanism for server selection. There are * a number of properties that you can set for each backend: * - * driver: (string) The Ingo_Driver:: driver to use to store the script on - * the backend server. Valid options: - * 'ldap' - LDAP server - * 'null' - No backend server - * 'timsieved' - Cyrus timsieved server - * 'vfs' - Use Horde VFS + * transport: (string) The Ingo_Transport driver to use to store the script on + * the backend server. Valid options: + * - 'ldap': LDAP server + * - 'null': No backend server + * - 'timsieved': Timsieved (managesieve) server + * - 'vfs': Use Horde VFS * * preferred: (string) This is the field that is used to choose which server * is used. The value for this field may be a single string or an @@ -16,24 +16,24 @@ * server. * * hordeauth: (mixed) One of the following: - * true - Ingo will attempt to use the user's existing credentials - * (the username/password they used to log in to Horde) to - * login to this source. (DEFAULT) - * 'full' - The username will be used unmodified. + * - true: Ingo will attempt to use the user's existing + * credentials (the username/password they used to log in + * to Horde) to login to this source. (DEFAULT) + * - 'full': The username will be used unmodified. * * params: (array) An array containing any additional information that the - * Ingo_Driver:: class needs. + * Ingo_Transport class needs. * * script: (string) The type of Ingo_Script driver this server uses. * Valid options: - * 'imap' - IMAP client side filtering (POP3 servers NOT - supported) - * 'maildrop' - Maildrop scripts - * 'procmail' - Procmail scripts - * 'sieve' - Sieve scripts + * - 'imap': IMAP client side filtering (POP3 servers NOT + * supported) + * - 'maildrop': Maildrop scripts + * - 'procmail': Procmail scripts + * - 'sieve': Sieve scripts * * scriptparams: (array) An array containing any additional information that - * the Ingo_Script:: driver needs. + * the Ingo_Script driver needs. * * shares: (boolean) Some drivers support sharing filter rules with other * users. Users can then configure filters for each other if they @@ -45,7 +45,7 @@ /* IMAP Example */ $backends['imap'] = array( - 'driver' => 'null', + 'transport' => 'null', 'preferred' => 'example.com', 'hordeauth' => true, 'params' => array(), @@ -56,7 +56,7 @@ $backends['imap'] = array( /* Maildrop Example */ $backends['maildrop'] = array( - 'driver' => 'vfs', + 'transport' => 'vfs', 'preferred' => 'example.com', 'hordeauth' => true, 'params' => array( @@ -114,7 +114,7 @@ $backends['maildrop'] = array( /* Procmail Example */ $backends['procmail'] = array( - 'driver' => 'vfs', + 'transport' => 'vfs', 'preferred' => 'example.com', 'hordeauth' => true, 'params' => array( @@ -187,7 +187,7 @@ $backends['procmail'] = array( /* Sieve Example */ $backends['sieve'] = array( - 'driver' => 'timsieved', + 'transport' => 'timsieved', 'preferred' => 'example.com', 'hordeauth' => true, 'params' => array( @@ -223,7 +223,7 @@ $backends['sieve'] = array( /* sivtest Example */ $backends['sivtest'] = array( - 'driver' => 'sivtest', + 'transport' => 'sivtest', 'preferred' => 'example.com', 'hordeauth' => true, 'params' => array( @@ -250,7 +250,7 @@ $backends['sivtest'] = array( /* Sun ONE/JES Example (LDAP/Sieve) */ $backends['ldapsieve'] = array( - 'driver' => 'ldap', + 'transport' => 'ldap', 'preferred' => 'example.com', 'hordeauth' => false, 'params' => array( @@ -319,7 +319,7 @@ if ($GLOBALS['conf']['kolab']['enabled']) { } $backends['kolab'] = array( - 'driver' => 'timsieved', + 'transport' => 'timsieved', 'preferred' => '', 'hordeauth' => 'full', 'params' => array( diff --git a/ingo/docs/CHANGES b/ingo/docs/CHANGES index 24330e491..0fc8960bd 100644 --- a/ingo/docs/CHANGES +++ b/ingo/docs/CHANGES @@ -2,8 +2,10 @@ v2.0-git -------- +[jan] Rename 'driver' configuration and classes to 'transport' to avoid + confusion with script and storage drivers. [mms] Use IMP API for IMAP actions. -[mms] Convert to Horde 5 standards. +[mms] Convert to Horde 4 standards. ---------- diff --git a/ingo/lib/Application.php b/ingo/lib/Application.php index 94042fc80..e750fe432 100644 --- a/ingo/lib/Application.php +++ b/ingo/lib/Application.php @@ -62,8 +62,8 @@ class Ingo_Application extends Horde_Registry_Application Ingo::createSession(); // Create shares if necessary. - $driver = Ingo::getDriver(); - if ($driver->supportShares()) { + $transport = Ingo::getTransport(); + if ($transport->supportShares()) { $GLOBALS['ingo_shares'] = $GLOBALS['injector']->getInstance('Horde_Share')->getScope(); $GLOBALS['all_rulesets'] = Ingo::listRulesets(); diff --git a/ingo/lib/Driver.php b/ingo/lib/Driver.php deleted file mode 100644 index 96482c0f3..000000000 --- a/ingo/lib/Driver.php +++ /dev/null @@ -1,83 +0,0 @@ - - * @package Ingo - */ -class Ingo_Driver -{ - /** - * Driver specific parameters - * - * @var array - */ - protected $_params = array( - 'username' => null, - 'password' => null - ); - - /** - * Whether this driver allows managing other users' rules. - * - * @var boolean - */ - protected $_support_shares = false; - - /** - * Attempts to return a concrete instance based on $driver. - * - * @param string $driver The type of concrete subclass to return. - * @param array $params A hash containing any additional configuration - * or connection parameters a subclass might need. - * - * @return Ingo_Driver The newly created concrete instance. - * @throws Ingo_Exception - */ - static public function factory($driver, $params = array()) - { - $class = __CLASS__ . '_' . ucfirst(basename($driver)); - - if (class_exists($class)) { - return new $class($params); - } - - throw new Ingo_Exception('Could not load driver.'); - } - - /** - * Constructor. - */ - public function __construct($params = array()) - { - $this->_params = array_merge($this->_params, $params); - } - - /** - * Sets a script running on the backend. - * - * @param string $script The filter script. - * - * @return boolean True on success, false if script can't be activated. - * @throws Ingo_Exception - */ - public function setScriptActive($script) - { - return false; - } - - /** - * Returns whether the driver supports managing other users' rules. - * - * @return boolean True if the driver supports shares. - */ - public function supportShares() - { - return ($this->_support_shares && - !empty($_SESSION['ingo']['backend']['shares'])); - } - -} diff --git a/ingo/lib/Driver/Ldap.php b/ingo/lib/Driver/Ldap.php deleted file mode 100644 index f4ec454e4..000000000 --- a/ingo/lib/Driver/Ldap.php +++ /dev/null @@ -1,254 +0,0 @@ - - * @package Ingo - */ -class Ingo_Driver_Ldap extends Ingo_Driver -{ - /** - * Constructor. - * - * @throws Ingo_Exception - */ - public function __construct($params = array()) - { - if (!Horde_Util::extensionExists('ldap')) { - throw new Ingo_Exception(_("LDAP support is required but the LDAP module is not available or not loaded.")); - } - - $default_params = array( - 'hostspec' => 'localhost', - 'port' => 389, - 'script_attribute' => 'mailSieveRuleSource' - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Create a DN from a DN template. - * This is done by substituting the username for %u and the 'dc=' - * components for %d. - * - * @param string $templ The DN template (from the config). - * - * @return string The resulting DN. - */ - protected function _substUser($templ) - { - $domain = ''; - $username = $this->_params['username']; - - if (strpos($username, '@') !== false) { - list($username, $domain) = explode('@', $username); - } - $domain = implode(', dc=', explode('.', $domain)); - if (!empty($domain)) { - $domain = 'dc=' . $domain; - } - - if (preg_match('/^\s|\s$|\s\s|[,+="\r\n<>#;]/', $username)) { - $username = '"' . str_replace('"', '\\"', $username) . '"'; - } - - return str_replace(array('%u', '%d'), - array($username, $domain), - $templ); - } - - /** - * Connect and bind to ldap server. - * - * @throws Ingo_Exception - */ - protected function _connect() - { - if (!($ldapcn = @ldap_connect($this->_params['hostspec'], - $this->_params['port']))) { - throw new Ingo_Exception(_("Connection failure")); - } - - /* Set the LDAP protocol version. */ - if (!empty($this->_params['version'])) { - @ldap_set_option($ldapcn, - LDAP_OPT_PROTOCOL_VERSION, - $this->_params['version']); - } - - /* Start TLS if we're using it. */ - if (!empty($this->_params['tls']) && - !@ldap_start_tls($ldapcn)) { - throw new Ingo_Exception(sprintf(_("STARTTLS failed: (%s) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - /* Bind to the server. */ - if (isset($this->_params['bind_dn'])) { - $bind_dn = $this->_substUser($this->_params['bind_dn']); - - $password = isset($this->_params['bind_password']) - ? $this->_params['bind_password'] - : $this->_params['password']; - - $bind_success = @ldap_bind($ldapcn, $bind_dn, $password); - } else { - $bind_success = @ldap_bind($ldapcn); - } - - if ($bind_success) { - return $ldapcn; - } - - - throw new Ingo_Exception(sprintf(_("Bind failed: (%s) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - /** - * Retrieve current user's scripts. - * - * @param resource $ldapcn The connection to the LDAP server. - * @param string $userDN Set to the user object's real DN. - * - * @return array Script sources list. - * @throws Ingo_Exception - */ - protected function _getScripts($ldapcn, &$userDN) - { - $attrs = array($this->_params['script_attribute'], 'dn'); - $filter = $this->_substUser($this->_params['script_filter']); - - /* Find the user object. */ - $sr = @ldap_search($ldapcn, $this->_params['script_base'], $filter, - $attrs); - if ($sr === false) { - throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - if (@ldap_count_entries($ldapcn, $sr) != 1) { - throw new Ingo_Exception(sprintf(_("Expected 1 object, got %d."), - ldap_count_entries($ldapcn, $sr))); - } - - $ent = @ldap_first_entry($ldapcn, $sr); - if ($ent === false) { - throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - /* Retrieve the user's DN. */ - $v = @ldap_get_dn($ldapcn, $ent); - if ($v === false) { - @ldap_free_result($sr); - throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - $userDN = $v; - - /* Retrieve the user's scripts. */ - $attrs = @ldap_get_attributes($ldapcn, $ent); - @ldap_free_result($sr); - if ($attrs === false) { - throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - /* Attribute can be in any case, and can have a ";binary" - * specifier. */ - $regexp = '/^' . preg_quote($this->_params['script_attribute'], '/') . - '(?:;.*)?$/i'; - unset($attrs['count']); - foreach ($attrs as $name => $values) { - if (preg_match($regexp, $name)) { - unset($values['count']); - return array_values($values); - } - } - - return array(); - } - - /** - * Sets a script running on the backend. - * - * @param string $script The sieve script. - * - * @throws Ingo_Exception - */ - protected function setScriptActive($script) - { - $ldapcn = $this->_connect(); - $values = $this->_getScripts($ldapcn, $userDN); - - $found = false; - foreach ($values as $i => $value) { - if (strpos($value, "# Sieve Filter\n") !== false) { - if (empty($script)) { - unset($values[$i]); - } else { - $values[$i] = $script; - } - $found = true; - break; - } - } - - if (!$found && !empty($script)) { - $values[] = $script; - } - - $replace = array(Horde_String::lower($this->_params['script_attribute']) => $values); - $r = empty($values) - ? @ldap_mod_del($ldapcn, $userDN, $replace) - : @ldap_mod_replace($ldapcn, $userDN, $replace); - - if (!$r) { - throw new Ingo_Exception(sprintf(_("Activating the script for \"%s\" failed: (%d) %s"), - $userDN, - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - @ldap_close($ldapcn); - - return true; - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - * - * @throws Ingo_Exception - */ - public function getScript() - { - $ldapcn = $this->_connect(); - $values = $this->_getScripts($ldapcn, $userDN); - - $script = ''; - foreach ($values as $value) { - if (strpos($value, "# Sieve Filter\n") !== false) { - $script = $value; - break; - } - } - - @ldap_close($ldapcn); - return $script; - } - -} diff --git a/ingo/lib/Driver/Null.php b/ingo/lib/Driver/Null.php deleted file mode 100644 index 231b6d8d0..000000000 --- a/ingo/lib/Driver/Null.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @package Ingo - */ - -class Ingo_Driver_Null extends Ingo_Driver -{ - /** - * Constructor. - */ - public function __construct($params = array()) - { - $this->_support_shares = true; - parent::__construct($params); - } - -} diff --git a/ingo/lib/Driver/Sivtest.php b/ingo/lib/Driver/Sivtest.php deleted file mode 100644 index fda72d82b..000000000 --- a/ingo/lib/Driver/Sivtest.php +++ /dev/null @@ -1,195 +0,0 @@ - - * - * See the enclosed file LICENSE for license information (ASL). If you - * did not receive this file, see http://www.horde.org/licenses/asl.php. - * - * @author Mike Cochrane - * @author Jan Schneider - * @author Liam Hoekenga - * @package Ingo - */ -class Ingo_Driver_Sivtest extends Ingo_Driver -{ - /** - * The Net_Sieve object. - * - * @var Net_Sieve - */ - protected $_sieve; - - /** - * Constructor. - */ - public function __construct($params = array()) - { - $default_params = array( - 'hostspec' => 'localhost', - 'logintype' => '', - 'port' => 2000, - 'scriptname' => 'ingo', - 'admin' => '', - 'usetls' => true, - 'command' => '', - 'socket' => '', - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Connect to the sieve server. - * - * @throws Ingo_Exception; - */ - protected function _connect() - { - if (!empty($this->_sieve)) { - return; - } - - $this->sivtestSocket($this->_params['username'], - $this->_params['password'], $this->_params['hostspec']); - $domain_socket = 'unix://' . $this->_params['socket']; - - $this->_sieve = new Net_Sieve($this->_params['username'], - $this->_params['password'], - $domain_socket, - 0, - null, - null, - false, - true, - $this->_params['usetls']); - - $res = $this->_sieve->getError(); - if ($res instanceof PEAR_Error) { - unset($this->_sieve); - throw new Ingo_Exception($res); - } - } - - /** - * Sets a script running on the backend. - * - * @param string $script The sieve script. - * - * @throws Ingo_Exception - */ - public function setScriptActive($script) - { - $this->_connect(); - - $res = $this->_sieve->haveSpace($this->_params['scriptname'], strlen($script)); - if ($res instanceof PEAR_Error) { - throw new Ingo_Exception($res); - } - - return $this->_sieve->installScript($this->_params['scriptname'], $script, true); - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - * @throws Ingo Exception - */ - public function getScript() - { - $this->_connect(); - return $this->_sieve->getScript($this->_sieve->getActive()); - } - - /** - * Used to figure out which Sieve server the script will be run - * on, and then open a GSSAPI authenticated socket to said server. - * - * @param string $username The username. - * @param string $password The password. - * @param string $hostspec The hostspec. - * - * @return TODO - * @throws Ingo_Exception - */ - public function sivtestSocket($username, $password, $hostspec) - { - $command = ''; - $error_return = ''; - - if (strtolower($this->_params['logintype']) == 'gssapi' - && isset($_SERVER['KRB5CCNAME'])) { - $command .= 'KRB5CCNAME=' . $_SERVER['KRB5CCNAME']; - } - - $domain_socket = 'unix://' . $this->_params['socket']; - - $command .= ' ' . $this->_params['command'] - . ' -m ' . $this->_params['logintype'] - . ' -u ' . $username - . ' -a ' . $username - . ' -w ' . $password - . ' -p ' . $this->_params['port'] - . ' -X ' . $this->_params['socket'] - . ' ' . $hostspec; - - $conn_attempts = 0; - while ($conn_attempts++ < 4) { - $attempts = 0; - if (!file_exists($this->_params['socket'])) { - exec($command . ' > /dev/null 2>&1'); - sleep(1); - while (!file_exists($this->_params['socket'])) { - usleep(200000); - if ($attempts++ > 5) { - $error_return = ': No socket after 10 seconds of trying!'; - continue 2; - } - } - } - $socket = new Net_Socket(); - $error = $socket->connect($domain_socket, 0, true, 30); - if (!($error instanceof PEAR_Error)) { - break; - } - - // We failed, break this connection. - unlink($this->_params['socket']); - } - - if (!empty($error_return)) { - throw new Ingo_Exception($error_return); - } - - $status = $socket->getStatus(); - if ($status instanceof PEAR_Error || $status['eof']) { - throw new Ingo_Exception(_("Failed to write to socket: (connection lost!)")); - } - - $error = $socket->writeLine("CAPABILITY"); - if ($error instanceof PEAR_Error) { - throw new Ingo_Exception(_("Failed to write to socket: " . $error->getMessage())); - } - - $result = $socket->readLine(); - if ($result instanceof PEAR_Error) { - throw new Ingo_Exception(_("Failed to read from socket: " . $error->getMessage())); - } - - if (preg_match('|^bye \(referral "(sieve://)?([^"]+)|i', - $result, $matches)) { - $socket->disconnect(); - - $this->sivtestSocket($username, $password, $matches[2]); - } else { - $socket->disconnect(); - exec($command . ' > /dev/null 2>&1'); - sleep(1); - } - } - -} diff --git a/ingo/lib/Driver/Timsieved.php b/ingo/lib/Driver/Timsieved.php deleted file mode 100644 index 42b33e782..000000000 --- a/ingo/lib/Driver/Timsieved.php +++ /dev/null @@ -1,135 +0,0 @@ - - * @author Jan Schneider - * @package Ingo - */ -class Ingo_Driver_Timsieved extends Ingo_Driver -{ - /** - * The Net_Sieve object. - * - * @var Net_Sieve - */ - protected $_sieve; - - /** - * Constructor. - */ - public function __construct($params = array()) - { - $this->_support_shares = true; - - $default_params = array( - 'hostspec' => 'localhost', - 'logintype' => 'PLAIN', - 'port' => 2000, - 'scriptname' => 'ingo', - 'admin' => '', - 'usetls' => true - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Connects to the sieve server. - * - * @throws Ingo_Exception - */ - protected function _connect() - { - if (!empty($this->_sieve)) { - return; - } - - $auth = empty($this->_params['admin']) - ? $this->_params['username'] - : $this->_params['admin']; - - $this->_sieve = new Net_Sieve($auth, - $this->_params['password'], - $this->_params['hostspec'], - $this->_params['port'], - $this->_params['logintype'], - Ingo::getUser(false), - $this->_params['debug'], - false, - $this->_params['usetls'], - null, - array($this, '_debug')); - - $res = $this->_sieve->getError(); - if ($res instanceof PEAR_Error) { - unset($this->_sieve); - throw new Ingo_Exception($res); - } - - /* BC for older Net_Sieve versions that don't allow specify the debug - * handler in the constructor. */ - if (!empty($this->_params['debug'])) { - $this->_sieve->setDebug(true, array($this, '_debug')); - } - } - - /** - * Routes the Sieve protocol log to the Horde log. - * - * @param Net_Sieve $sieve A Net_Sieve object. - * @param string $message The tracked Sieve communication. - */ - protected function _debug($sieve, $message) - { - Horde::logMessage($message, 'DEBUG'); - } - - /** - * Sets a script running on the backend. - * - * @param string $script The sieve script. - * - * @return mixed True on success. - * @throws Ingo_Exception - */ - public function setScriptActive($script) - { - $res = $this->_connect(); - - if (!strlen($script)) { - return $this->_sieve->setActive(''); - } - - $res = $this->_sieve->haveSpace($this->_params['scriptname'], strlen($script)); - if ($res instanceof PEAR_Error) { - throw new Ingo_Exception($res); - } - - return $this->_sieve->installScript($this->_params['scriptname'], - $script, true); - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - * @throws Ingo_Exception - */ - public function getScript() - { - $res = $this->_connect(); - $active = $this->_sieve->getActive(); - - return empty($active) - ? '' - : $this->_sieve->getScript($active); - } - -} diff --git a/ingo/lib/Driver/Vfs.php b/ingo/lib/Driver/Vfs.php deleted file mode 100644 index 7bd9f4133..000000000 --- a/ingo/lib/Driver/Vfs.php +++ /dev/null @@ -1,144 +0,0 @@ - - * @author Jan Schneider - * @package Ingo - */ -class Ingo_Driver_Vfs extends Ingo_Driver -{ - /** - * Constructs a new VFS-based storage driver. - * - * @param array $params A hash containing driver parameters. - */ - public function __construct($params = array()) - { - $this->_support_shares = true; - - $default_params = array( - 'hostspec' => 'localhost', - 'port' => 21, - 'filename' => '.ingo_filter', - 'vfstype' => 'ftp', - 'vfs_path' => '', - 'vfs_forward_path' => '', - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Sets a script running on the backend. - * - * @param string $script The filter script. - * - * @return mixed True on success. - * @throws Ingo_Exception - */ - public function setScriptActive($script) - { - $this->_connect(); - - try { - if (empty($script)) { - $this->_vfs->deleteFile($this->_params['vfs_path'], $this->_params['filename']); - } else { - $this->_vfs->writeData($this->_params['vfs_path'], $this->_params['filename'], $script, true); - } - } catch (VFS_Exception $e) { - throw new Ingo_Exception($e); - } - - if (isset($this->_params['file_perms']) && !empty($script)) { - try { - $this->_vfs->changePermissions($this->_params['vfs_path'], $this->_params['filename'], $this->_params['file_perms']); - } catch (VFS_Exception $e) { - throw new Ingo_Exception($e); - } - } - - // Get the backend; necessary if a .forward is needed for - // procmail. - $backend = Ingo::getBackend(); - if (($backend['script'] == 'procmail') && - isset($backend['params']['forward_file']) && - isset($backend['params']['forward_string'])) { - try { - if (empty($script)) { - $this->_vfs->deleteFile($this->_params['vfs_forward_path'], $backend['params']['forward_file']); - } else { - $this->_vfs->writeData($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $backend['params']['forward_string'], true); - } - } catch (VFS_Exception $e) { - throw new Ingo_Exception($e); - } - - if (isset($this->_params['file_perms']) && !empty($script)) { - try { - $this->_vfs->changePermissions($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $this->_params['file_perms']); - } catch (VFS_Exception $e) { - throw new Ingo_Exception($e); - } - } - } - - return true; - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - * @throws Ingo_Exception - */ - public function getScript() - { - $this->_connect(); - return $this->_vfs->read($this->_params['vfs_path'], $this->_params['filename']); - } - - /** - * Connect to the VFS server. - * - * @throws Ingo_Exception - */ - protected function _connect() - { - /* Do variable substitution. */ - if (!empty($this->_params['vfs_path'])) { - $user = Ingo::getUser(); - $domain = Ingo::getDomain(); - if ($_SESSION['ingo']['backend']['hordeauth'] !== 'full') { - $pos = strpos($user, '@'); - if ($pos !== false) { - $domain = substr($user, $pos + 1); - $user = substr($user, 0, $pos); - } - } - $this->_params['vfs_path'] = str_replace( - array('%u', '%d', '%U'), - array($user, $domain, $this->_params['username']), - $this->_params['vfs_path']); - } - - if (!empty($this->_vfs)) { - return true; - } - - try { - $this->_vfs = VFS::singleton($this->_params['vfstype'], $this->_params); - } catch (VFS_Exception $e) { - $error = new Ingo_Exception($this->_vfs); - unset($this->_vfs); - throw $error; - } - } - -} diff --git a/ingo/lib/Ingo.php b/ingo/lib/Ingo.php index 90039b5d8..9f31b8850 100644 --- a/ingo/lib/Ingo.php +++ b/ingo/lib/Ingo.php @@ -212,10 +212,10 @@ class Ingo */ static public function activateScript($script, $deactivate = false) { - $driver = self::getDriver(); + $transport = self::getTransport(); try { - $res = $driver->setScriptActive($script); + $res = $transport->setScriptActive($script); } catch (Ingo_Exception $e) { $msg = ($deactivate) ? _("There was an error deactivating the script.") @@ -243,7 +243,7 @@ class Ingo */ static public function getScript() { - return self::getDriver()->getScript(); + return self::getTransport()->getScript(); } /** @@ -311,8 +311,8 @@ class Ingo if (empty($backend['script'])) { throw new Ingo_Exception(sprintf(_("No \"%s\" element found in backend configuration."), 'script')); - } elseif (empty($backend['driver'])) { - throw new Ingo_Exception(sprintf(_("No \"%s\" element found in backend configuration."), 'driver')); + } elseif (empty($backend['transport'])) { + throw new Ingo_Exception(sprintf(_("No \"%s\" element found in backend configuration."), 'transport')); } /* Make sure the 'params' entry exists. */ @@ -336,12 +336,12 @@ class Ingo } /** - * Returns an instance of the configured driver. + * Returns an instance of the configured transport driver. * - * @return Ingo_Driver The configured driver. + * @return Ingo_Transport The configured driver. * @throws Ingo_Exception */ - static public function getDriver() + static public function getTransport() { $params = $_SESSION['ingo']['backend']['params']; @@ -358,7 +358,7 @@ class Ingo $params['password'] = $GLOBALS['registry']->getAuthCredential('password'); } - return Ingo_Driver::factory($_SESSION['ingo']['backend']['driver'], $params); + return Ingo_Transport::factory($_SESSION['ingo']['backend']['transport'], $params); } /** diff --git a/ingo/lib/Script/Maildrop/Recipe.php b/ingo/lib/Script/Maildrop/Recipe.php index 4e8d3679d..c4859e923 100644 --- a/ingo/lib/Script/Maildrop/Recipe.php +++ b/ingo/lib/Script/Maildrop/Recipe.php @@ -129,9 +129,9 @@ class Ingo_Script_Maildrop_Recipe // Writing vacation.msg file $reason = Horde_Mime::encode($params['action-value']['reason'], $scriptparams['charset']); - $driver = Ingo::getDriver(); - $driver->_connect(); - $result = $driver->_vfs->writeData($driver->_params['vfs_path'], 'vacation.msg', $reason, true); + $transport = Ingo::getTransport(); + $transport->_connect(); + $result = $transport->_vfs->writeData($transport->_params['vfs_path'], 'vacation.msg', $reason, true); // Rule : Do not send responses to bulk or list messages if ($params['action-value']['ignorelist'] == 1) { diff --git a/ingo/lib/Transport.php b/ingo/lib/Transport.php new file mode 100644 index 000000000..a97ad93da --- /dev/null +++ b/ingo/lib/Transport.php @@ -0,0 +1,83 @@ + + * @package Ingo + */ +class Ingo_Transport +{ + /** + * Driver specific parameters + * + * @var array + */ + protected $_params = array( + 'username' => null, + 'password' => null + ); + + /** + * Whether this driver allows managing other users' rules. + * + * @var boolean + */ + protected $_support_shares = false; + + /** + * Attempts to return a concrete instance based on $driver. + * + * @param string $driver The type of concrete subclass to return. + * @param array $params A hash containing any additional configuration + * or connection parameters a subclass might need. + * + * @return Ingo_Transport The newly created concrete instance. + * @throws Ingo_Exception + */ + static public function factory($driver, $params = array()) + { + $class = __CLASS__ . '_' . ucfirst(basename($driver)); + + if (class_exists($class)) { + return new $class($params); + } + + throw new Ingo_Exception('Could not load driver.'); + } + + /** + * Constructor. + */ + public function __construct($params = array()) + { + $this->_params = array_merge($this->_params, $params); + } + + /** + * Sets a script running on the backend. + * + * @param string $script The filter script. + * + * @return boolean True on success, false if script can't be activated. + * @throws Ingo_Exception + */ + public function setScriptActive($script) + { + return false; + } + + /** + * Returns whether the driver supports managing other users' rules. + * + * @return boolean True if the driver supports shares. + */ + public function supportShares() + { + return ($this->_support_shares && + !empty($_SESSION['ingo']['backend']['shares'])); + } + +} diff --git a/ingo/lib/Transport/Ldap.php b/ingo/lib/Transport/Ldap.php new file mode 100644 index 000000000..13263cf82 --- /dev/null +++ b/ingo/lib/Transport/Ldap.php @@ -0,0 +1,254 @@ + + * @package Ingo + */ +class Ingo_Transport_Ldap extends Ingo_Transport +{ + /** + * Constructor. + * + * @throws Ingo_Exception + */ + public function __construct($params = array()) + { + if (!Horde_Util::extensionExists('ldap')) { + throw new Ingo_Exception(_("LDAP support is required but the LDAP module is not available or not loaded.")); + } + + $default_params = array( + 'hostspec' => 'localhost', + 'port' => 389, + 'script_attribute' => 'mailSieveRuleSource' + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Create a DN from a DN template. + * This is done by substituting the username for %u and the 'dc=' + * components for %d. + * + * @param string $templ The DN template (from the config). + * + * @return string The resulting DN. + */ + protected function _substUser($templ) + { + $domain = ''; + $username = $this->_params['username']; + + if (strpos($username, '@') !== false) { + list($username, $domain) = explode('@', $username); + } + $domain = implode(', dc=', explode('.', $domain)); + if (!empty($domain)) { + $domain = 'dc=' . $domain; + } + + if (preg_match('/^\s|\s$|\s\s|[,+="\r\n<>#;]/', $username)) { + $username = '"' . str_replace('"', '\\"', $username) . '"'; + } + + return str_replace(array('%u', '%d'), + array($username, $domain), + $templ); + } + + /** + * Connect and bind to ldap server. + * + * @throws Ingo_Exception + */ + protected function _connect() + { + if (!($ldapcn = @ldap_connect($this->_params['hostspec'], + $this->_params['port']))) { + throw new Ingo_Exception(_("Connection failure")); + } + + /* Set the LDAP protocol version. */ + if (!empty($this->_params['version'])) { + @ldap_set_option($ldapcn, + LDAP_OPT_PROTOCOL_VERSION, + $this->_params['version']); + } + + /* Start TLS if we're using it. */ + if (!empty($this->_params['tls']) && + !@ldap_start_tls($ldapcn)) { + throw new Ingo_Exception(sprintf(_("STARTTLS failed: (%s) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + /* Bind to the server. */ + if (isset($this->_params['bind_dn'])) { + $bind_dn = $this->_substUser($this->_params['bind_dn']); + + $password = isset($this->_params['bind_password']) + ? $this->_params['bind_password'] + : $this->_params['password']; + + $bind_success = @ldap_bind($ldapcn, $bind_dn, $password); + } else { + $bind_success = @ldap_bind($ldapcn); + } + + if ($bind_success) { + return $ldapcn; + } + + + throw new Ingo_Exception(sprintf(_("Bind failed: (%s) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + /** + * Retrieve current user's scripts. + * + * @param resource $ldapcn The connection to the LDAP server. + * @param string $userDN Set to the user object's real DN. + * + * @return array Script sources list. + * @throws Ingo_Exception + */ + protected function _getScripts($ldapcn, &$userDN) + { + $attrs = array($this->_params['script_attribute'], 'dn'); + $filter = $this->_substUser($this->_params['script_filter']); + + /* Find the user object. */ + $sr = @ldap_search($ldapcn, $this->_params['script_base'], $filter, + $attrs); + if ($sr === false) { + throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + if (@ldap_count_entries($ldapcn, $sr) != 1) { + throw new Ingo_Exception(sprintf(_("Expected 1 object, got %d."), + ldap_count_entries($ldapcn, $sr))); + } + + $ent = @ldap_first_entry($ldapcn, $sr); + if ($ent === false) { + throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + /* Retrieve the user's DN. */ + $v = @ldap_get_dn($ldapcn, $ent); + if ($v === false) { + @ldap_free_result($sr); + throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + $userDN = $v; + + /* Retrieve the user's scripts. */ + $attrs = @ldap_get_attributes($ldapcn, $ent); + @ldap_free_result($sr); + if ($attrs === false) { + throw new Ingo_Exception(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + /* Attribute can be in any case, and can have a ";binary" + * specifier. */ + $regexp = '/^' . preg_quote($this->_params['script_attribute'], '/') . + '(?:;.*)?$/i'; + unset($attrs['count']); + foreach ($attrs as $name => $values) { + if (preg_match($regexp, $name)) { + unset($values['count']); + return array_values($values); + } + } + + return array(); + } + + /** + * Sets a script running on the backend. + * + * @param string $script The sieve script. + * + * @throws Ingo_Exception + */ + protected function setScriptActive($script) + { + $ldapcn = $this->_connect(); + $values = $this->_getScripts($ldapcn, $userDN); + + $found = false; + foreach ($values as $i => $value) { + if (strpos($value, "# Sieve Filter\n") !== false) { + if (empty($script)) { + unset($values[$i]); + } else { + $values[$i] = $script; + } + $found = true; + break; + } + } + + if (!$found && !empty($script)) { + $values[] = $script; + } + + $replace = array(Horde_String::lower($this->_params['script_attribute']) => $values); + $r = empty($values) + ? @ldap_mod_del($ldapcn, $userDN, $replace) + : @ldap_mod_replace($ldapcn, $userDN, $replace); + + if (!$r) { + throw new Ingo_Exception(sprintf(_("Activating the script for \"%s\" failed: (%d) %s"), + $userDN, + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + @ldap_close($ldapcn); + + return true; + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + * + * @throws Ingo_Exception + */ + public function getScript() + { + $ldapcn = $this->_connect(); + $values = $this->_getScripts($ldapcn, $userDN); + + $script = ''; + foreach ($values as $value) { + if (strpos($value, "# Sieve Filter\n") !== false) { + $script = $value; + break; + } + } + + @ldap_close($ldapcn); + return $script; + } + +} diff --git a/ingo/lib/Transport/Null.php b/ingo/lib/Transport/Null.php new file mode 100644 index 000000000..ea501c0c2 --- /dev/null +++ b/ingo/lib/Transport/Null.php @@ -0,0 +1,24 @@ + + * @package Ingo + */ + +class Ingo_Transport_Null extends Ingo_Transport +{ + /** + * Constructor. + */ + public function __construct($params = array()) + { + $this->_support_shares = true; + parent::__construct($params); + } + +} diff --git a/ingo/lib/Transport/Sivtest.php b/ingo/lib/Transport/Sivtest.php new file mode 100644 index 000000000..4f631d3e9 --- /dev/null +++ b/ingo/lib/Transport/Sivtest.php @@ -0,0 +1,195 @@ + + * + * See the enclosed file LICENSE for license information (ASL). If you + * did not receive this file, see http://www.horde.org/licenses/asl.php. + * + * @author Mike Cochrane + * @author Jan Schneider + * @author Liam Hoekenga + * @package Ingo + */ +class Ingo_Transport_Sivtest extends Ingo_Transport +{ + /** + * The Net_Sieve object. + * + * @var Net_Sieve + */ + protected $_sieve; + + /** + * Constructor. + */ + public function __construct($params = array()) + { + $default_params = array( + 'hostspec' => 'localhost', + 'logintype' => '', + 'port' => 2000, + 'scriptname' => 'ingo', + 'admin' => '', + 'usetls' => true, + 'command' => '', + 'socket' => '', + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Connect to the sieve server. + * + * @throws Ingo_Exception; + */ + protected function _connect() + { + if (!empty($this->_sieve)) { + return; + } + + $this->sivtestSocket($this->_params['username'], + $this->_params['password'], $this->_params['hostspec']); + $domain_socket = 'unix://' . $this->_params['socket']; + + $this->_sieve = new Net_Sieve($this->_params['username'], + $this->_params['password'], + $domain_socket, + 0, + null, + null, + false, + true, + $this->_params['usetls']); + + $res = $this->_sieve->getError(); + if ($res instanceof PEAR_Error) { + unset($this->_sieve); + throw new Ingo_Exception($res); + } + } + + /** + * Sets a script running on the backend. + * + * @param string $script The sieve script. + * + * @throws Ingo_Exception + */ + public function setScriptActive($script) + { + $this->_connect(); + + $res = $this->_sieve->haveSpace($this->_params['scriptname'], strlen($script)); + if ($res instanceof PEAR_Error) { + throw new Ingo_Exception($res); + } + + return $this->_sieve->installScript($this->_params['scriptname'], $script, true); + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + * @throws Ingo Exception + */ + public function getScript() + { + $this->_connect(); + return $this->_sieve->getScript($this->_sieve->getActive()); + } + + /** + * Used to figure out which Sieve server the script will be run + * on, and then open a GSSAPI authenticated socket to said server. + * + * @param string $username The username. + * @param string $password The password. + * @param string $hostspec The hostspec. + * + * @return TODO + * @throws Ingo_Exception + */ + public function sivtestSocket($username, $password, $hostspec) + { + $command = ''; + $error_return = ''; + + if (strtolower($this->_params['logintype']) == 'gssapi' + && isset($_SERVER['KRB5CCNAME'])) { + $command .= 'KRB5CCNAME=' . $_SERVER['KRB5CCNAME']; + } + + $domain_socket = 'unix://' . $this->_params['socket']; + + $command .= ' ' . $this->_params['command'] + . ' -m ' . $this->_params['logintype'] + . ' -u ' . $username + . ' -a ' . $username + . ' -w ' . $password + . ' -p ' . $this->_params['port'] + . ' -X ' . $this->_params['socket'] + . ' ' . $hostspec; + + $conn_attempts = 0; + while ($conn_attempts++ < 4) { + $attempts = 0; + if (!file_exists($this->_params['socket'])) { + exec($command . ' > /dev/null 2>&1'); + sleep(1); + while (!file_exists($this->_params['socket'])) { + usleep(200000); + if ($attempts++ > 5) { + $error_return = ': No socket after 10 seconds of trying!'; + continue 2; + } + } + } + $socket = new Net_Socket(); + $error = $socket->connect($domain_socket, 0, true, 30); + if (!($error instanceof PEAR_Error)) { + break; + } + + // We failed, break this connection. + unlink($this->_params['socket']); + } + + if (!empty($error_return)) { + throw new Ingo_Exception($error_return); + } + + $status = $socket->getStatus(); + if ($status instanceof PEAR_Error || $status['eof']) { + throw new Ingo_Exception(_("Failed to write to socket: (connection lost!)")); + } + + $error = $socket->writeLine("CAPABILITY"); + if ($error instanceof PEAR_Error) { + throw new Ingo_Exception(_("Failed to write to socket: " . $error->getMessage())); + } + + $result = $socket->readLine(); + if ($result instanceof PEAR_Error) { + throw new Ingo_Exception(_("Failed to read from socket: " . $error->getMessage())); + } + + if (preg_match('|^bye \(referral "(sieve://)?([^"]+)|i', + $result, $matches)) { + $socket->disconnect(); + + $this->sivtestSocket($username, $password, $matches[2]); + } else { + $socket->disconnect(); + exec($command . ' > /dev/null 2>&1'); + sleep(1); + } + } + +} diff --git a/ingo/lib/Transport/Timsieved.php b/ingo/lib/Transport/Timsieved.php new file mode 100644 index 000000000..6b187e0ab --- /dev/null +++ b/ingo/lib/Transport/Timsieved.php @@ -0,0 +1,135 @@ + + * @author Jan Schneider + * @package Ingo + */ +class Ingo_Transport_Timsieved extends Ingo_Transport +{ + /** + * The Net_Sieve object. + * + * @var Net_Sieve + */ + protected $_sieve; + + /** + * Constructor. + */ + public function __construct($params = array()) + { + $this->_support_shares = true; + + $default_params = array( + 'hostspec' => 'localhost', + 'logintype' => 'PLAIN', + 'port' => 2000, + 'scriptname' => 'ingo', + 'admin' => '', + 'usetls' => true + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Connects to the sieve server. + * + * @throws Ingo_Exception + */ + protected function _connect() + { + if (!empty($this->_sieve)) { + return; + } + + $auth = empty($this->_params['admin']) + ? $this->_params['username'] + : $this->_params['admin']; + + $this->_sieve = new Net_Sieve($auth, + $this->_params['password'], + $this->_params['hostspec'], + $this->_params['port'], + $this->_params['logintype'], + Ingo::getUser(false), + $this->_params['debug'], + false, + $this->_params['usetls'], + null, + array($this, '_debug')); + + $res = $this->_sieve->getError(); + if ($res instanceof PEAR_Error) { + unset($this->_sieve); + throw new Ingo_Exception($res); + } + + /* BC for older Net_Sieve versions that don't allow specify the debug + * handler in the constructor. */ + if (!empty($this->_params['debug'])) { + $this->_sieve->setDebug(true, array($this, '_debug')); + } + } + + /** + * Routes the Sieve protocol log to the Horde log. + * + * @param Net_Sieve $sieve A Net_Sieve object. + * @param string $message The tracked Sieve communication. + */ + protected function _debug($sieve, $message) + { + Horde::logMessage($message, 'DEBUG'); + } + + /** + * Sets a script running on the backend. + * + * @param string $script The sieve script. + * + * @return mixed True on success. + * @throws Ingo_Exception + */ + public function setScriptActive($script) + { + $res = $this->_connect(); + + if (!strlen($script)) { + return $this->_sieve->setActive(''); + } + + $res = $this->_sieve->haveSpace($this->_params['scriptname'], strlen($script)); + if ($res instanceof PEAR_Error) { + throw new Ingo_Exception($res); + } + + return $this->_sieve->installScript($this->_params['scriptname'], + $script, true); + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + * @throws Ingo_Exception + */ + public function getScript() + { + $res = $this->_connect(); + $active = $this->_sieve->getActive(); + + return empty($active) + ? '' + : $this->_sieve->getScript($active); + } + +} diff --git a/ingo/lib/Transport/Vfs.php b/ingo/lib/Transport/Vfs.php new file mode 100644 index 000000000..c128bde11 --- /dev/null +++ b/ingo/lib/Transport/Vfs.php @@ -0,0 +1,144 @@ + + * @author Jan Schneider + * @package Ingo + */ +class Ingo_Transport_Vfs extends Ingo_Transport +{ + /** + * Constructs a new VFS-based storage driver. + * + * @param array $params A hash containing driver parameters. + */ + public function __construct($params = array()) + { + $this->_support_shares = true; + + $default_params = array( + 'hostspec' => 'localhost', + 'port' => 21, + 'filename' => '.ingo_filter', + 'vfstype' => 'ftp', + 'vfs_path' => '', + 'vfs_forward_path' => '', + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Sets a script running on the backend. + * + * @param string $script The filter script. + * + * @return mixed True on success. + * @throws Ingo_Exception + */ + public function setScriptActive($script) + { + $this->_connect(); + + try { + if (empty($script)) { + $this->_vfs->deleteFile($this->_params['vfs_path'], $this->_params['filename']); + } else { + $this->_vfs->writeData($this->_params['vfs_path'], $this->_params['filename'], $script, true); + } + } catch (VFS_Exception $e) { + throw new Ingo_Exception($e); + } + + if (isset($this->_params['file_perms']) && !empty($script)) { + try { + $this->_vfs->changePermissions($this->_params['vfs_path'], $this->_params['filename'], $this->_params['file_perms']); + } catch (VFS_Exception $e) { + throw new Ingo_Exception($e); + } + } + + // Get the backend; necessary if a .forward is needed for + // procmail. + $backend = Ingo::getBackend(); + if (($backend['script'] == 'procmail') && + isset($backend['params']['forward_file']) && + isset($backend['params']['forward_string'])) { + try { + if (empty($script)) { + $this->_vfs->deleteFile($this->_params['vfs_forward_path'], $backend['params']['forward_file']); + } else { + $this->_vfs->writeData($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $backend['params']['forward_string'], true); + } + } catch (VFS_Exception $e) { + throw new Ingo_Exception($e); + } + + if (isset($this->_params['file_perms']) && !empty($script)) { + try { + $this->_vfs->changePermissions($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $this->_params['file_perms']); + } catch (VFS_Exception $e) { + throw new Ingo_Exception($e); + } + } + } + + return true; + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + * @throws Ingo_Exception + */ + public function getScript() + { + $this->_connect(); + return $this->_vfs->read($this->_params['vfs_path'], $this->_params['filename']); + } + + /** + * Connect to the VFS server. + * + * @throws Ingo_Exception + */ + protected function _connect() + { + /* Do variable substitution. */ + if (!empty($this->_params['vfs_path'])) { + $user = Ingo::getUser(); + $domain = Ingo::getDomain(); + if ($_SESSION['ingo']['backend']['hordeauth'] !== 'full') { + $pos = strpos($user, '@'); + if ($pos !== false) { + $domain = substr($user, $pos + 1); + $user = substr($user, 0, $pos); + } + } + $this->_params['vfs_path'] = str_replace( + array('%u', '%d', '%U'), + array($user, $domain, $this->_params['username']), + $this->_params['vfs_path']); + } + + if (!empty($this->_vfs)) { + return true; + } + + try { + $this->_vfs = VFS::singleton($this->_params['vfstype'], $this->_params); + } catch (VFS_Exception $e) { + $error = new Ingo_Exception($this->_vfs); + unset($this->_vfs); + throw $error; + } + } + +}