implemented combined email backend
authorLars Kneschke <l.kneschke@metaways.de>
Tue, 3 Mar 2015 06:56:07 +0000 (07:56 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 4 Mar 2015 13:39:45 +0000 (14:39 +0100)
- for dovecot and postfix
- postfix authenticates against dovecot
- shared mailboxes table
- separate aliases and forwards table

Change-Id: I7b54f0ac62aef26848f3396012690deb481d2318
Reviewed-on: http://gerrit.tine20.com/customers/1707
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Setup/js/EmailPanel.js
tine20/Tinebase/EmailUser.php
tine20/Tinebase/EmailUser/Imap/DovecotCombined.php [new file with mode: 0644]
tine20/Tinebase/EmailUser/Smtp/PostfixCombined.php [new file with mode: 0644]
tine20/Tinebase/EmailUser/Sql.php
tine20/Tinebase/User/Plugin/Abstract.php

index 1549ab8..3cf31af 100644 (file)
@@ -4,7 +4,7 @@
  * @package     Setup
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schüle <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2009-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2009-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -147,7 +147,7 @@ Tine.Setup.EmailPanel = Ext.extend(Tine.Tinebase.widgets.form.ConfigPanel, {
         };
         
         // imap combo
-        backendComboConfig.store = [['standard', this.app.i18n._('Standard IMAP')], ['dbmail', 'DBmail  MySQL'], ['ldap_imap', 'DBmail Ldap'], ['cyrus', 'Cyrus'], ['dovecot_imap', 'Dovecot MySQL']];
+        backendComboConfig.store = [['standard', this.app.i18n._('Standard IMAP')], ['dbmail', 'DBmail  MySQL'], ['ldap_imap', 'DBmail Ldap'], ['cyrus', 'Cyrus'], ['dovecot_imap', 'Dovecot MySQL'], ['dovecotcombined', 'Dovecot SQL (combined schema)']];
         backendComboConfig.name = 'imap_backend';
         backendComboConfig.listeners = {
             scope: this,
@@ -157,7 +157,7 @@ Tine.Setup.EmailPanel = Ext.extend(Tine.Tinebase.widgets.form.ConfigPanel, {
         this.imapBackendCombo = new Ext.form.ComboBox(backendComboConfig);
         
         // smtp combo
-        backendComboConfig.store = [['standard', this.app.i18n._('Standard SMTP')], ['postfix', 'Postfix MySQL'], ['Ldapsmtpmail', 'Ldap (only mail attribute)'], ['ldapSmtp', 'Postfix Ldap (dbmail schema)'], ['ldapSmtpQmail', 'Postfix Ldap (qmail schema)']];
+        backendComboConfig.store = [['standard', this.app.i18n._('Standard SMTP')], ['postfix', 'Postfix MySQL'], ['postfixcombined', 'Postfix SQL (combined schema)'], ['Ldapsmtpmail', 'Ldap (only mail attribute)'], ['ldapSmtp', 'Postfix Ldap (dbmail schema)'], ['ldapSmtpQmail', 'Postfix Ldap (qmail schema)']];
         backendComboConfig.name = 'smtp_backend';
         backendComboConfig.listeners = {
             scope: this,
@@ -277,6 +277,16 @@ Tine.Setup.EmailPanel = Ext.extend(Tine.Tinebase.widgets.form.ConfigPanel, {
                             xtype: 'textfield'
                         },
                         items: this.getDbConfigFields('imap', 'dovecot').concat(this.getDovecotExtraConfig('imap'))
+                    }, {
+                        // dovecot combined with postfix config options
+                        id: this.imapBackendIdPrefix + 'dovecotcombined',
+                        layout: 'form',
+                        autoHeight: 'auto',
+                        defaults: {
+                            width: 300,
+                            xtype: 'textfield'
+                        },
+                        items: this.getDbAdapterField('imap', 'dovecotcombined').concat(this.getDbConfigFields('imap', 'dovecotcombined'))
                     }]
                 }
             ]
@@ -353,7 +363,7 @@ Tine.Setup.EmailPanel = Ext.extend(Tine.Tinebase.widgets.form.ConfigPanel, {
                         id: this.smtpBackendIdPrefix + 'ldap_smtp_mail',
                         layout: 'form',
                         items: []
-                    },{
+                    }, {
                         // postfix config options
                         id: this.smtpBackendIdPrefix + 'postfix',
                         layout: 'form',
@@ -365,6 +375,16 @@ Tine.Setup.EmailPanel = Ext.extend(Tine.Tinebase.widgets.form.ConfigPanel, {
                         items: this.getDbConfigFields('smtp', 'postfix')
                     }, {
                         // postfix config options
+                        id: this.smtpBackendIdPrefix + 'postfixcombined',
+                        layout: 'form',
+                        autoHeight: 'auto',
+                        defaults: {
+                            width: 300,
+                            xtype: 'textfield'
+                        },
+                        items: []
+                    }, {
+                        // postfix config options
                         id: this.smtpBackendIdPrefix + 'ldap_smtp',
                         layout: 'form',
                         autoHeight: 'auto',
@@ -417,6 +437,34 @@ Tine.Setup.EmailPanel = Ext.extend(Tine.Tinebase.widgets.form.ConfigPanel, {
     },
     
     /**
+     * get db adapter field
+     * 
+     * @param {String} type1 (imap, smtp)
+     * @param {String} type2 (dbmail, postfix, ...)
+     * @return {Array}
+     */
+    getDbAdapterField: function (type1, type2) {
+        var typeString = (this.showType) ? (Ext.util.Format.capitalize(type2) + ' ') : '';
+        
+        return [{
+            name          : type1 + '_' + type2 + '_adapter',
+            fieldLabel    : typeString + this.app.i18n._('Backend'),
+            typeAhead     : false,
+            triggerAction : 'all',
+            lazyRender    : true,
+            editable      : false,
+            mode          : 'local',
+            xtype         : 'combo',
+            listWidth     : 300,
+            value         : 'pdo_mysql',
+            store: [
+                ['pdo_mysql', 'MySQL'],
+                ['pdo_pgsql', 'PostgreSQL']
+            ],
+        }];
+    },
+    
+    /**
      * get db config fields
      * 
      * @param {String} type1 (imap, smtp)
@@ -474,7 +522,7 @@ Tine.Setup.EmailPanel = Ext.extend(Tine.Tinebase.widgets.form.ConfigPanel, {
             mode          : 'local',
             xtype: 'combo',
             listWidth: 300,
-            value: 'PLAIN-MD5',
+            value: 'SSHA256',
             store: [
                 ['PLAIN-MD5',      this.app.i18n._('PLAIN-MD5')],
                 ['MD5-CRYPT',    this.app.i18n._('MD5-CRYPT')],
index 46d2074..206777d 100644 (file)
@@ -40,7 +40,7 @@ class Tinebase_EmailUser
      * 
      * @staticvar string
      */
-    const DOVECOT_IMAP_COMBINED    = 'Dovecot_imap_combined';
+    const DOVECOT_COMBINED    = 'Dovecotcombined';
     
     /**
      * postfix backend const
@@ -54,7 +54,7 @@ class Tinebase_EmailUser
      * 
      * @staticvar string
      */
-    const POSTFIX_COMBINED    = 'Postfix_combined';
+    const POSTFIX_COMBINED    = 'Postfixcombined';
     
     /**
      * imap ldap backend const
@@ -157,7 +157,7 @@ class Tinebase_EmailUser
                     self::$_backends[$_type] = new Tinebase_EmailUser_Imap_Dbmail();
                 }
                 break;
-            
+                
             case self::CYRUS:
                 if (!isset(self::$_backends[$_type])) {
                     self::$_backends[$_type] = new Tinebase_EmailUser_Imap_Cyrus();
@@ -200,7 +200,7 @@ class Tinebase_EmailUser
                 }
                 break;
                 
-            case self::DOVECOT_IMAP_COMBINED:
+            case self::DOVECOT_COMBINED:
                 if (!isset(self::$_backends[$_type])) {
                     self::$_backends[$_type] = new Tinebase_EmailUser_Imap_DovecotCombined();
                 }
@@ -218,50 +218,29 @@ class Tinebase_EmailUser
     /**
      * returns the configured backend
      * 
-     * @param string $_configType
+     * @param string $configType
      * @return string
      * @throws Tinebase_Exception_NotFound
      */
-    public static function getConfiguredBackend($_configType = Tinebase_Config::IMAP)
+    public static function getConfiguredBackend($configType = Tinebase_Config::IMAP)
     {
-        $result = '';
-        
-        $config = self::getConfig($_configType);
+        $config = self::getConfig($configType);
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($config, TRUE));
         
-        if (isset($config['backend'])) {
-            $backend = ucfirst(strtolower($config['backend']));
-            switch ($_configType) {
-                case Tinebase_Config::IMAP:
-                    if ($backend == self::DBMAIL) {
-                        $result = self::DBMAIL;
-                    } else if ($backend == self::LDAP_IMAP) {
-                        $result = self::LDAP_IMAP;
-                    } else if ($backend == self::CYRUS) {
-                        $result = self::CYRUS;
-                    } else if ($backend == self::DOVECOT_IMAP) {
-                        $result = self::DOVECOT_IMAP;
-                    } 
-                    break;
-                case Tinebase_Config::SMTP:
-                    if ($backend == self::POSTFIX) {
-                        $result = self::POSTFIX;
-                    } else if ($backend == self::LDAP_SMTP) {
-                        $result = self::LDAP_SMTP;
-                    } else if ($backend == self::LDAP_SMTP_MAIL) {
-                        $result = self::LDAP_SMTP_MAIL;
-                    } else if ($backend == self::LDAP_SMTP_QMAIL) {
-                        $result = self::LDAP_SMTP_QMAIL;
-                    }
-                    break;
-            }
+        if (!isset($config['backend'])) {
+            throw new Tinebase_Exception_NotFound("No backend in config for type $configType found.");
         }
         
-        if (empty($result)) {
-            throw new Tinebase_Exception_NotFound("Config for type $_configType / $backend not found.");
+        $backend = ucfirst(strtolower($config['backend']));
+        
+        if (!in_array($backend, array(
+            self::DBMAIL, self::LDAP_IMAP, self::CYRUS, self::DOVECOT_IMAP, self::DOVECOT_COMBINED,
+            self::POSTFIX, self::POSTFIX_COMBINED, self::LDAP_SMTP, self::LDAP_SMTP_MAIL, self::LDAP_SMTP_QMAIL
+        ))) {
+            throw new Tinebase_Exception_NotFound("Config for type $configType / $backend not found.");
         }
         
-        return $result;
+        return $backend;
     }
     
     /**
diff --git a/tine20/Tinebase/EmailUser/Imap/DovecotCombined.php b/tine20/Tinebase/EmailUser/Imap/DovecotCombined.php
new file mode 100644 (file)
index 0000000..a47629c
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Tinebase
+ * @subpackage  EmailUser
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ */
+
+/**
+ * plugin to handle dovecot imap accounts
+ * 
+ * @package    Tinebase
+ * @subpackage EmailUser
+ */
+class Tinebase_EmailUser_Imap_DovecotCombined extends Tinebase_EmailUser_Sql
+{
+    /**
+     * quotas table name with prefix
+     *
+     * @var string
+     */
+    protected $_quotasTable = 'quotas';
+    
+    /**
+     * email user config
+     * 
+     * @var array 
+     */
+    protected $_config = array(
+        'prefix'            => null,
+        'userTable'         => 'mailboxes',
+        'quotaTable'        => 'usage',
+        'emailScheme'       => 'SSHA256',
+        'domain'            => null
+    );
+    
+    /**
+     * user properties mapping
+     *
+     * @var array
+     */
+    protected $_propertyMapping = array(
+        'emailUserId'       => 'id',
+        'emailUsername'     => 'loginname',
+        'emailPassword'     => 'password',
+        'emailAddress'      => 'email',
+        'emailForwardOnly'  => 'forward_only',
+        #'emailLastLogin'    => 'last_login',
+        #'emailMailQuota'    => 'quota_bytes',
+        #'emailSieveQuota'   => 'quota_message',
+    
+        #'emailMailSize'     => 'storage',
+        #'emailSieveSize'    => 'messages',
+
+        // makes mapping data to _config easier
+        'emailHome'            => 'home'
+    );
+    
+    /**
+     * Dovecot readonly
+     * 
+     * @var array
+     */
+    protected $_readOnlyFields = array(
+        'emailMailSize',
+        'emailSieveSize',
+        'emailLastLogin',
+    );
+    
+    /**
+     * the constructor
+     */
+    public function __construct(array $_options = array())
+    {
+        $this->_configKey = Tinebase_Config::IMAP;
+        $this->_subconfigKey = 'dovecotcombined';
+        
+        parent::__construct($_options);
+        
+        // set domain from imap config
+        $emailConfig = Tinebase_Config::getInstance()->get($this->_configKey, new Tinebase_Config_Struct())->toArray();
+        $this->_config['domain'] = !empty($emailConfig['domain']) ? $emailConfig['domain'] : null;
+    }
+    
+    /**
+     * interceptor before add
+     * 
+     * @param array $emailUserData
+     */
+    protected function _beforeAddOrUpdate(&$emailUserData)
+    {
+        // add configured domain to domains table
+        $select = $this->_db->select()
+            ->from(array('domains'), array('name'))
+            ->where($this->_db->quoteIdentifier('domains.name') . ' = ?', $this->_config['domain']);
+        
+        $stmt = $this->_db->query($select);
+        $domains = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
+        
+        // did we find all domains in domains table?
+        if (count($domains) < 1) {
+            $this->_db->insert('domains', array(
+                'id'       => Tinebase_Record_Abstract::generateUID(),
+                'name'     => $this->_config['domain'],
+                'backupmx' => 0,
+                'active'   => 1
+            ));
+        }
+        
+        $emailUserData['last_modified'] = Tinebase_DateTime::now()->format(Tinebase_Record_Abstract::ISO8601LONG);
+    }
+    
+    /**
+     * delete user by id
+     * 
+     * @param string $id
+     */
+    protected function _deleteUserById($id)
+    {
+        $where = array(
+            $this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?' => $id,
+            $this->_db->quoteIdentifier($this->_userTable . '.domain') . ' = ?'          => $this->_config['domain']
+        );
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
+            . ' ' . print_r($where, TRUE));
+        
+        $this->_db->update($this->_userTable, array('is_deleted' => '1', 'last_modified' => Tinebase_DateTime::now()->getIso()), $where);
+    }
+    
+    /**
+     * get the basic select object to fetch records from the database
+     *  
+     * @param  array|string|Zend_Db_Expr  $_cols        columns to get, * per default
+     * @param  boolean                    $_getDeleted  get deleted records (if modlog is active)
+     * @return Zend_Db_Select
+     */
+    protected function _getSelect($_cols = '*', $_getDeleted = FALSE)
+    {
+        $userIDMap = $this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']);
+        
+        $select = $this->_db->select()
+        
+            ->from(array('mailboxes' => $this->_userTable))
+
+            // Left Join Quotas Table
+            #->joinLeft(
+            #    array($this->_quotasTable), // table
+            #    '(' . $this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUsername']) .  ' = ' . // ON (left)
+            #        $this->_db->quoteIdentifier($this->_quotasTable . '.' . $this->_propertyMapping['emailUsername']) . ')', // ON (right)
+            #    array( // Select
+            #        $this->_propertyMapping['emailMailSize']  => $this->_quotasTable . '.' . $this->_propertyMapping['emailMailSize'], // emailMailSize
+            #        $this->_propertyMapping['emailSieveSize'] => $this->_quotasTable . '.' . $this->_propertyMapping['emailSieveSize'] // emailSieveSize
+            #    ) 
+            #)
+            
+            // Only want 1 user (shouldn't be more than 1 anyway)
+            ->limit(1)
+            
+            // limit query to enabled domains
+            ->where($this->_db->quoteIdentifier($this->_userTable . '.domain') .     ' = ?', $this->_config['domain'])
+            ->where($this->_db->quoteIdentifier($this->_userTable . '.is_deleted') . ' = ?', '0');
+            
+        return $select;
+    }
+    
+    /**
+     * converts raw data from adapter into a single record / do mapping
+     *
+     * @param  array                    $_data
+     * @return Tinebase_Model_EmailUser
+     */
+    protected function _rawDataToRecord(array $_rawdata)
+    {
+        $data = array();
+        
+        foreach ($_rawdata as $key => $value) {
+            $keyMapping = array_search($key, $this->_propertyMapping);
+            if ($keyMapping !== FALSE) {
+                switch($keyMapping) {
+                    case 'emailPassword':
+                    case 'emailAliases':
+                    case 'emailForwards':
+                    case 'emailForwardOnly':
+                    case 'emailAddress':
+                        // do nothing
+                        break;
+                    case 'emailMailQuota':
+                    case 'emailSieveQuota':
+                        $data[$keyMapping] = $value > 0 ? $value : null;
+                        break;
+                    case 'emailMailSize':
+                        $data[$keyMapping] = $value > 0 ? round($value/1048576, 2) : 0;
+                        break;
+                    default: 
+                        $data[$keyMapping] = $value;
+                        break;
+                }
+            }
+        }
+        
+        return new Tinebase_Model_EmailUser($data, true);
+    }
+     
+    /**
+     * returns array of raw Dovecot data
+     *
+     * @param  Tinebase_Model_FullUser  $_user
+     * @param  Tinebase_Model_FullUser  $_newUserProperties
+     * @return array
+     */
+    protected function _recordToRawData(Tinebase_Model_FullUser $_user, Tinebase_Model_FullUser $_newUserProperties)
+    {
+        $rawData = array();
+        
+        if (isset($_newUserProperties->imapUser)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_newUserProperties->imapUser->toArray(), true));
+            
+            foreach ($_newUserProperties->imapUser as $key => $value) {
+                $property = (isset($this->_propertyMapping[$key]) || array_key_exists($key, $this->_propertyMapping)) ? $this->_propertyMapping[$key] : false;
+                if ($property && ! in_array($key, $this->_readOnlyFields)) {
+                    switch ($key) {
+                        case 'emailUserId':
+                        case 'emailUsername':
+                            // set later
+                            break;
+                            
+                        case 'emailPassword':
+                            $rawData[$property] = Hash_Password::generate($this->_config['emailScheme'], $value);
+                            break;
+                            
+                        case 'emailMailQuota':
+                            $rawData[$property] = (empty($value)) ? 0 : $value;
+                            break;
+                            
+                        default:
+                            $rawData[$property] = $value;
+                            break;
+                    }
+                }
+            }
+        }
+        
+        $rawData[$this->_propertyMapping['emailAddress']]     = $_user->accountEmailAddress;
+        $rawData[$this->_propertyMapping['emailForwardOnly']] = '0'; // will be overwritten later
+        $rawData[$this->_propertyMapping['emailUserId']]      = $_user->getId();
+        $rawData[$this->_propertyMapping['emailUsername']]    = $_user->accountLoginName;
+        $rawData[$this->_propertyMapping['emailHome']]        = '/' . $_user->accountLoginName . '_' . substr($_user->getId(), 0,8);
+        
+        $rawData['domain']     = $this->_config['domain'];
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($rawData, true));
+        
+        return $rawData;
+    }
+}
diff --git a/tine20/Tinebase/EmailUser/Smtp/PostfixCombined.php b/tine20/Tinebase/EmailUser/Smtp/PostfixCombined.php
new file mode 100644 (file)
index 0000000..470ea19
--- /dev/null
@@ -0,0 +1,443 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Tinebase
+ * @subpackage  EmailUser
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ */
+
+ /**
+ * plugin to handle postfix smtp accounts
+ * 
+ * @package    Tinebase
+ * @subpackage EmailUser
+ */
+class Tinebase_EmailUser_Smtp_PostfixCombined extends Tinebase_EmailUser_Sql
+{
+    /**
+     * destination table name with prefix
+     *
+     * @var string
+     */
+    protected $_destinationTable = NULL;
+    protected $_forwardTable = 'forwards';
+    
+    /**
+     * postfix config
+     * 
+     * @var array 
+     */
+    protected $_config = array(
+        'prefix'            => '',
+        'userTable'         => 'mailboxes',
+        'destinationTable'  => 'aliases',
+        'domain'            => null,
+        'alloweddomains'    => array()
+    );
+
+    /**
+     * user properties mapping
+     *
+     * @var array
+     */
+    protected $_propertyMapping = array(
+        'emailUserId'       => 'id',
+        'emailAddress'      => 'email',
+        'emailForwardOnly'  => 'forward_only',
+        'emailUsername'     => 'loginname',
+        'emailAliases'      => 'aliases',
+        'emailForwards'     => 'forwards'
+    );
+    
+    /**
+     * the constructor
+     */
+    public function __construct(array $_options = array())
+    {
+        $this->_configKey    = Tinebase_Config::SMTP;
+
+        parent::__construct($_options);
+        
+        $smtpConfig = Tinebase_Config::getInstance()->get($this->_configKey, new Tinebase_Config_Struct())->toArray();
+        
+        // set domain from smtp config
+        $this->_config['domain'] = !empty($smtpConfig['primarydomain']) ? $smtpConfig['primarydomain'] : null;
+        
+        // add allowed domains
+        if (! empty($smtpConfig['primarydomain'])) {
+            $this->_config['alloweddomains'] = array($smtpConfig['primarydomain']);
+            if (! empty($smtpConfig['secondarydomains'])) {
+                // merge primary and secondary domains and split secondary domains + trim whitespaces
+                $this->_config['alloweddomains'] = array_merge($this->_config['alloweddomains'], preg_split('/\s*,\s*/', $smtpConfig['secondarydomains']));
+            } 
+        }
+        
+        $this->_destinationTable = $this->_config['prefix'] . $this->_config['destinationTable'];
+    }
+    
+    /**
+     * set database connection shared with Tinebase_EmailUser::DOVECOT_IMAP_COMBINED backend
+     * 
+     * @param array $_config
+     */
+    protected function _getDb($_config)
+    {
+        $dovecotCombined = Tinebase_EmailUser::factory(Tinebase_EmailUser::DOVECOT_COMBINED);
+        
+        $this->_db = $dovecotCombined->getDb();
+    }
+    
+    /**
+     * get the basic select object to fetch records from the database
+     *  
+     * @param  array|string|Zend_Db_Expr  $_cols        columns to get, * per default
+     * @param  boolean                    $_getDeleted  get deleted records (if modlog is active)
+     * @return Zend_Db_Select
+     */
+    protected function _getSelect($_cols = '*', $_getDeleted = FALSE)
+    {
+        $userIDMap = $this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']);
+        
+        $select = $this->_db->select()
+            ->from(array('mailboxes' => $this->_userTable))
+            ->group($this->_userTable . '.' . $this->_propertyMapping['emailUserId'])
+            // Only want 1 user (shouldn't be more than 1 anyway)
+            ->limit(1)
+            
+            // select aliases from aliases table
+            ->joinLeft(
+                /* table  */ array('aliases' => $this->_destinationTable),
+                /* on     */ $userIDMap . ' = ' . $this->_db->quoteIdentifier('aliases.mailbox_id'),
+                /* select */ array($this->_propertyMapping['emailAliases'] => $this->_dbCommand->getAggregate('aliases.alias')))
+         
+            // select forwards from forwards table
+            ->joinLeft(
+                /* table  */ array('forwards' => $this->_forwardTable),
+                /* on     */ $userIDMap . ' = ' . $this->_db->quoteIdentifier('forwards.mailbox_id'),
+                /* select */ array($this->_propertyMapping['emailForwards'] => $this->_dbCommand->getAggregate('forwards.forward')))
+            
+            // limit query to enabled domains
+            ->where($this->_db->quoteIdentifier($this->_userTable . '.domain') .     ' = ?', $this->_config['domain'])
+            ->where($this->_db->quoteIdentifier($this->_userTable . '.is_deleted') . ' = ?', '0');
+            
+        return $select;
+    }
+    
+    /**
+    * interceptor before add
+    *
+    * @param array $emailUserData
+    */
+    protected function _beforeAddOrUpdate(&$emailUserData)
+    {
+        // add all configured domains to domains table
+        $select = $this->_db->select()
+            ->from(array('domains'), array('name'))
+            ->where($this->_db->quoteIdentifier('domains.name') . ' IN (?)', $this->_config['alloweddomains']);
+        
+        $stmt = $this->_db->query($select);
+        $domains = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
+        
+        // did we find all domains in domains table?
+        if (count($domains) < count($this->_config['alloweddomains'])) {
+            foreach (array_diff($this->_config['alloweddomains'], $domains) as $domain) {
+                $this->_db->insert('domains', array(
+                    'id'       => Tinebase_Record_Abstract::generateUID(),
+                    'name'     => $domain,
+                    'backupmx' => 0,
+                    'active'   => 1
+                ));
+            }
+        }
+        
+        $emailUserData['last_modified'] = Tinebase_DateTime::now()->format(Tinebase_Record_Abstract::ISO8601LONG);
+        
+        unset($emailUserData[$this->_propertyMapping['emailForwards']]);
+        unset($emailUserData[$this->_propertyMapping['emailAliases']]);
+    }
+    
+    /**
+    * interceptor after add
+    *
+    * @param array $emailUserData
+    */
+    protected function _afterAddOrUpdate(&$emailUserData)
+    {
+        $this->_setAliasesAndForwards($emailUserData);
+    }
+    
+    /**
+     * set email aliases and forwards
+     * 
+     * removes all aliases for user
+     * creates default email->email alias if not forward only
+     * creates aliases
+     * creates forwards
+     * 
+     * @param  array  $_smtpSettings  as returned from _recordToRawData
+     * @return void
+     */
+    protected function _setAliasesAndForwards($_smtpSettings)
+    {
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
+            . ' Setting alias/forward for ' . print_r($_smtpSettings, true));
+        
+        $this->_removeAliasesAndForwards($_smtpSettings[$this->_propertyMapping['emailUserId']]);
+        
+        $this->_setAliases($_smtpSettings);
+        $this->_setForwards($_smtpSettings);
+    }
+    
+    /**
+     * remove all current aliases and forwards for user
+     * 
+     * @param string $userId
+     */
+    protected function _removeAliasesAndForwards($userId)
+    {
+        $where = array(
+            $this->_db->quoteInto($this->_db->quoteIdentifier('mailbox_id') . ' = ?', $userId)
+        );
+        
+        $this->_db->delete($this->_destinationTable, $where);
+        $this->_db->delete($this->_forwardTable, $where);
+    }
+    
+    /**
+     * set aliases
+     * 
+     * @param array $_smtpSettings
+     */
+    protected function _setAliases($_smtpSettings)
+    {
+        if (! ((isset($_smtpSettings[$this->_propertyMapping['emailAliases']]) || array_key_exists($this->_propertyMapping['emailAliases'], $_smtpSettings)) && is_array($_smtpSettings[$this->_propertyMapping['emailAliases']]))) {
+            return;
+        }
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Setting aliases for '
+            . $_smtpSettings[$this->_propertyMapping['emailUsername']] . ': ' . print_r($_smtpSettings[$this->_propertyMapping['emailAliases']], TRUE));
+        
+        $userId = $_smtpSettings[$this->_propertyMapping['emailUserId']];
+        
+        foreach ($_smtpSettings[$this->_propertyMapping['emailAliases']] as $aliasAddress) {
+            // check if in primary or secondary domains
+            if (! empty($aliasAddress) && $this->_checkDomain($aliasAddress)) {
+                $this->_db->insert($this->_destinationTable, array(
+                    'id'         => Tinebase_Record_Abstract::generateUID(),
+                    'mailbox_id' => $userId,
+                    'alias'      => $aliasAddress
+                ));
+            }
+        }
+    }
+    
+    /**
+     * check if forward addresses exist
+     * 
+     * @param array $_smtpSettings
+     * @return boolean
+     */
+    protected function _hasForwards($_smtpSettings)
+    {
+        return ((isset($_smtpSettings[$this->_propertyMapping['emailForwards']]) || array_key_exists($this->_propertyMapping['emailForwards'], $_smtpSettings)) && is_array($_smtpSettings[$this->_propertyMapping['emailForwards']]));
+    }
+
+    /**
+     * set forwards
+     * 
+     * @param array $_smtpSettings
+     */
+    protected function _setForwards($_smtpSettings)
+    {
+        if (! $this->_hasForwards($_smtpSettings)) {
+            return;
+        }
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
+            . ' Setting forwards for ' . $_smtpSettings[$this->_propertyMapping['emailUsername']] . ': ' . print_r($_smtpSettings[$this->_propertyMapping['emailForwards']], TRUE));
+        
+        foreach ($_smtpSettings[$this->_propertyMapping['emailForwards']] as $forwardAddress) {
+            if (! empty($forwardAddress)) {
+                // create email -> forward
+                $this->_db->insert($this->_forwardTable, array(
+                    'id'         => Tinebase_Record_Abstract::generateUID(),
+                    'mailbox_id' => $_smtpSettings[$this->_propertyMapping['emailUserId']],
+                    'forward'    => $forwardAddress
+                ));
+            }
+        }
+    }
+    
+    /**
+     * converts raw data from adapter into a single record / do mapping
+     *
+     * @param  array $_data
+     * @return Tinebase_Record_Abstract
+     */
+    protected function _rawDataToRecord(array $_rawdata)
+    {
+        $data = array();
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
+            . ' raw data: ' . print_r($_rawdata, true));
+        
+        foreach ($_rawdata as $key => $value) {
+            $keyMapping = array_search($key, $this->_propertyMapping);
+            if ($keyMapping !== FALSE) {
+                switch ($keyMapping) {
+                    case 'emailPassword':
+                        // do nothing
+                        break;
+                    
+                    case 'emailAliases':
+                    case 'emailForwards':
+                        $data[$keyMapping] = explode(',', $value);
+                        // Get rid of TineEmail -> username mapping.
+                        $tineEmailAlias = array_search($_rawdata[$this->_propertyMapping['emailUsername']], $data[$keyMapping]);
+                        if ($tineEmailAlias !== false) {
+                            unset($data[$keyMapping][$tineEmailAlias]);
+                            $data[$keyMapping] = array_values($data[$keyMapping]);
+                        }
+                        // sanitize aliases & forwards
+                        if (count($data[$keyMapping]) == 1 && empty($data[$keyMapping][0])) {
+                            $data[$keyMapping] = array();
+                        }
+                        break;
+                        
+                    case 'emailForwardOnly':
+                        $data[$keyMapping] = (bool)$value;
+                        break;
+                        
+                    default: 
+                        $data[$keyMapping] = $value;
+                        break;
+                }
+            }
+        }
+        
+        $emailUser = new Tinebase_Model_EmailUser($data, TRUE);
+        
+        return $emailUser;
+    }
+    
+    /**
+     * returns array of raw email user data
+     *
+     * @param  Tinebase_Model_EmailUser $_user
+     * @param  Tinebase_Model_EmailUser $_newUserProperties
+     * @throws Tinebase_Exception_UnexpectedValue
+     * @return array
+     * 
+     * @todo   validate domains of aliases too
+     */
+    protected function _recordToRawData(Tinebase_Model_FullUser $_user, Tinebase_Model_FullUser $_newUserProperties)
+    {
+        $rawData = array();
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_newUserProperties->toArray(), true));
+        
+        if (isset($_newUserProperties->smtpUser)) {
+            foreach ($_newUserProperties->smtpUser as $key => $value) {
+                $property = (isset($this->_propertyMapping[$key]) || array_key_exists($key, $this->_propertyMapping)) ? $this->_propertyMapping[$key] : false;
+                if ($property) {
+                    switch ($key) {
+                        case 'emailAliases':
+                            $rawData[$property] = array();
+                            
+                            foreach((array)$value as $address) {
+                                if ($this->_checkDomain($address) === true) {
+                                    $rawData[$property][] = $address;
+                                }
+                            }
+                            break;
+                            
+                        case 'emailForwards':
+                            $rawData[$property] = is_array($value) ? $value : array();
+                            
+                            break;
+                            
+                        default:
+                            $rawData[$property] = $value;
+                            break;
+                    }
+                }
+            }
+        }
+        
+        if (!empty($_user->accountEmailAddress)) {
+            $this->_checkDomain($_user->accountEmailAddress, TRUE);
+        }
+        
+        $rawData[$this->_propertyMapping['emailAddress']]  = $_user->accountEmailAddress;
+        $rawData[$this->_propertyMapping['emailUserId']]   = $_user->getId();
+        $rawData[$this->_propertyMapping['emailUsername']] = $_user->accountLoginName;
+        
+        if (empty($rawData[$this->_propertyMapping['emailAddress']])) {
+            $rawData[$this->_propertyMapping['emailAliases']]  = null;
+            $rawData[$this->_propertyMapping['emailForwards']] = null;
+        }
+        
+        if (empty($rawData[$this->_propertyMapping['emailForwards']])) {
+            $rawData[$this->_propertyMapping['emailForwardOnly']] = 0;
+        }
+        
+        $rawData['domain']     = $this->_config['domain'];
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($rawData, true));
+        
+        return $rawData;
+    }
+    
+    /**
+     * check if email address is in allowed domains
+     * 
+     * @param string $_email
+     * @param boolean $_throwException
+     * @return boolean
+     * @throws Tinebase_Exception_Record_NotAllowed
+     */
+    protected function _checkDomain($_email, $_throwException = false)
+    {
+        $result = true;
+        
+        if (! empty($this->_config['alloweddomains'])) {
+
+            list($user, $domain) = explode('@', $_email, 2);
+            
+            if (! in_array($domain, $this->_config['alloweddomains'])) {
+                if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Email address ' . $_email . ' not in allowed domains!');
+                
+                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Allowed domains: ' . print_r($this->_config['alloweddomains'], TRUE));
+                
+                if ($_throwException) {
+                    throw new Tinebase_Exception_UnexpectedValue('Email address not in allowed domains!');
+                } else {
+                    $result = false;
+                }
+            }
+        }
+        
+        return $result;
+    }
+    
+    /**
+     * delete user by id
+     * 
+     * @param string $id
+     */
+    protected function _deleteUserById($id)
+    {
+        $where = array(
+            $this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?' => $id,
+            $this->_db->quoteIdentifier($this->_userTable . '.domain') . ' = ?'          => $this->_config['domain']
+        );
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
+            . ' ' . print_r($where, TRUE));
+        
+        $this->_db->update($this->_userTable, array('is_deleted' => '1', 'last_modified' => Tinebase_DateTime::now()->getIso()), $where);
+    }
+}
index 2f10d93..797f8f7 100644 (file)
@@ -66,7 +66,7 @@ abstract class Tinebase_EmailUser_Sql extends Tinebase_User_Plugin_Abstract
      */
     public function __construct(array $_options = array())
     {
-        if ($this->_configKey === NULL || $this->_subconfigKey === NULL) {
+        if ($this->_configKey === NULL) {
             throw new Tinebase_Exception_UnexpectedValue('Need config keys for this backend');
         }
         
@@ -74,7 +74,9 @@ abstract class Tinebase_EmailUser_Sql extends Tinebase_User_Plugin_Abstract
         $emailConfig = Tinebase_Config::getInstance()->get($this->_configKey, new Tinebase_Config_Struct())->toArray();
         
         // merge _config and email backend config
-        $this->_config = array_merge($emailConfig[$this->_subconfigKey], $this->_config);
+        if ($this->_subconfigKey) {
+            $this->_config = array_merge($emailConfig[$this->_subconfigKey], $this->_config);
+        }
         
         // _tablename (for example "dovecot_users")
         $this->_userTable = $this->_config['prefix'] . $this->_config['userTable'];
index bbb9304..f136637 100644 (file)
@@ -92,9 +92,9 @@ abstract class Tinebase_User_Plugin_Abstract implements Tinebase_User_Plugin_Sql
     protected function _getDb($_config)
     {
         $tine20DbConfig = Tinebase_Core::getDb()->getConfig();
-        $tine20DbConfig['adapter'] = str_replace('Tinebase_Backend_Sql_Adapter_', '', get_class(Tinebase_Core::getDb()));
+        $tine20DbConfig['adapter'] = strtolower(str_replace('Tinebase_Backend_Sql_Adapter_', '', get_class(Tinebase_Core::getDb())));
         
-        if ($this->_config['adapter']  == $tine20DbConfig['adapter'] &&
+        if (strtolower($this->_config['adapter']) == $tine20DbConfig['adapter'] &&
             $this->_config['host']     == $tine20DbConfig['host'] && 
             $this->_config['dbname']   == $tine20DbConfig['dbname'] &&
             $this->_config['username'] == $tine20DbConfig['username']