Driver -> Transport
authorJan Schneider <jan@horde.org>
Mon, 23 Aug 2010 14:09:15 +0000 (16:09 +0200)
committerJan Schneider <jan@horde.org>
Mon, 23 Aug 2010 14:09:15 +0000 (16:09 +0200)
17 files changed:
ingo/config/backends.php.dist
ingo/docs/CHANGES
ingo/lib/Application.php
ingo/lib/Driver.php [deleted file]
ingo/lib/Driver/Ldap.php [deleted file]
ingo/lib/Driver/Null.php [deleted file]
ingo/lib/Driver/Sivtest.php [deleted file]
ingo/lib/Driver/Timsieved.php [deleted file]
ingo/lib/Driver/Vfs.php [deleted file]
ingo/lib/Ingo.php
ingo/lib/Script/Maildrop/Recipe.php
ingo/lib/Transport.php [new file with mode: 0644]
ingo/lib/Transport/Ldap.php [new file with mode: 0644]
ingo/lib/Transport/Null.php [new file with mode: 0644]
ingo/lib/Transport/Sivtest.php [new file with mode: 0644]
ingo/lib/Transport/Timsieved.php [new file with mode: 0644]
ingo/lib/Transport/Vfs.php [new file with mode: 0644]

index 4306b92..873b736 100644 (file)
@@ -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
  *            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(
index 24330e4..0fc8960 100644 (file)
@@ -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.
 
 
 ----------
index 94042fc..e750fe4 100644 (file)
@@ -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 (file)
index 96482c0..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-/**
- * Ingo_Driver:: defines an API to activate filter scripts on a server.
- *
- * 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 <mike@graftonhall.co.nz>
- * @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 (file)
index f4ec454..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-<?php
-/**
- * Ingo_Driver_Ldap:: implements the Sieve_Driver api to allow scripts to be
- * installed and set active via an LDAP server.
- *
- * 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  Jason M. Felice <jason.m.felice@gmail.com>
- * @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 (file)
index 231b6d8..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-/**
- * Ingo_Driver_Null:: implements a null api -- useful for just testing
- * the UI and storage.
- *
- * 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  Brent J. Nordquist <bjn@horde.org>
- * @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 (file)
index fda72d8..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-<?php
-/**
- * Ingo_Driver_Sivtest:: implements the Sieve_Driver api to allow scripts to
- * be installed and set active via the Cyrus sivtest command line utility.
- *
- * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
- * Copyright 2004-2007 Liam Hoekenga <liamr@umich.edu>
- *
- * 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 <mike@graftonhall.co.nz>
- * @author  Jan Schneider <jan@horde.org>
- * @author  Liam Hoekenga <liamr@umich.edu>
- * @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 (file)
index 42b33e7..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-/**
- * Ingo_Driver_Timsieved:: implements the Sieve_Driver api to allow scripts to
- * be installed and set active via a Cyrus timsieved server.
- *
- * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
- *
- * 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 <mike@graftonhall.co.nz>
- * @author  Jan Schneider <jan@horde.org>
- * @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 (file)
index 7bd9f41..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-<?php
-/**
- * Ingo_Driver_Vfs:: implements an Ingo storage driver using Horde VFS.
- *
- * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
- *
- * 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  Brent J. Nordquist <bjn@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @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;
-        }
-    }
-
-}
index 90039b5..9f31b88 100644 (file)
@@ -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);
     }
 
     /**
index 4e8d367..c4859e9 100644 (file)
@@ -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 (file)
index 0000000..a97ad93
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Ingo_Transport defines an API to activate filter scripts on a server.
+ *
+ * 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 <mike@graftonhall.co.nz>
+ * @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 (file)
index 0000000..13263cf
--- /dev/null
@@ -0,0 +1,254 @@
+<?php
+/**
+ * Ingo_Transport_Ldap implements the Sieve_Driver api to allow scripts to be
+ * installed and set active via an LDAP server.
+ *
+ * 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  Jason M. Felice <jason.m.felice@gmail.com>
+ * @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 (file)
index 0000000..ea501c0
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Ingo_Transport_Null implements a null api -- useful for just testing
+ * the UI and storage.
+ *
+ * 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  Brent J. Nordquist <bjn@horde.org>
+ * @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 (file)
index 0000000..4f631d3
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+/**
+ * Ingo_Transport_Sivtest implements the Sieve_Driver api to allow scripts to
+ * be installed and set active via the Cyrus sivtest command line utility.
+ *
+ * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
+ * Copyright 2004-2007 Liam Hoekenga <liamr@umich.edu>
+ *
+ * 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 <mike@graftonhall.co.nz>
+ * @author  Jan Schneider <jan@horde.org>
+ * @author  Liam Hoekenga <liamr@umich.edu>
+ * @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 (file)
index 0000000..6b187e0
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Ingo_Transport_Timsieved implements the Sieve_Driver api to allow scripts
+ * to be installed and set active via a Cyrus timsieved server.
+ *
+ * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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 <mike@graftonhall.co.nz>
+ * @author  Jan Schneider <jan@horde.org>
+ * @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 (file)
index 0000000..c128bde
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Ingo_Transport_Vfs implements an Ingo storage driver using Horde VFS.
+ *
+ * Copyright 2003-2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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  Brent J. Nordquist <bjn@horde.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @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;
+        }
+    }
+
+}