2d859346305b5fcf81e10623d8829c0e0174014e
[tine20] / tine20 / Tinebase / EmailUser / Imap / Dbmail.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  EmailUser
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schüle <p.schuele@metaways.de>
10  * 
11  */
12
13 /**
14  * plugin to handle Dbmail imap accounts 
15  * 
16  * @package    Tinebase
17  * @subpackage EmailUser
18  * 
19  * @todo generalize some logic and move it to abstract parent class
20  */
21 class Tinebase_EmailUser_Imap_Dbmail extends Tinebase_User_Plugin_Abstract
22 {
23     /**
24      * @var Zend_Db_Adapter
25      */
26     protected $_db = NULL;
27     
28     /**
29      * user table name with prefix
30      *
31      * @var string
32      */
33     protected $_userTable = NULL;
34
35     /**
36      * client id
37      *
38      * @var string
39      */
40     protected $_clientId = NULL;
41     
42     /**
43      * dbmail config
44      * 
45      * @var array 
46      * 
47      * @todo add those to imap config?
48      */
49     protected $_config = array(
50         'prefix'       => 'dbmail_',
51         'userTable'    => 'users',
52         'emailScheme'  => 'md5',
53         'mailboxTable' => 'mailboxes',
54         'emailGID'     => null
55     );
56
57     /**
58      * user properties mapping
59      *
60      * @var array
61      */
62     protected $_propertyMapping = array(
63         'emailUserId'       => 'user_idnr',
64         'emailUsername'     => 'userid',
65         'emailPassword'     => 'passwd',
66         'emailGID'          => 'client_idnr', 
67         'emailLastLogin'    => 'last_login',
68         
69         'emailMailQuota'    => 'maxmail_size',
70         'emailMailSize'     => 'curmail_size',
71         'emailSieveQuota'   => 'maxsieve_size',
72         'emailSieveSize'    => 'cursieve_size',
73     
74         // makes mapping data to _config easier
75         'emailScheme'       => 'encryption_type',
76     );
77     
78     /**
79      * dbmail readonly
80      * 
81      * @var array
82      */
83     protected $_readOnlyFields = array(
84         'emailMailSize',
85         'emailSieveSize',
86         'emailLastLogin',
87     );
88     
89     /**
90      * stores if dbmail_users has tine20_userid column
91      * 
92      * @var boolean
93      */
94     protected $_hasTine20Userid = false;
95     
96     /**
97      * the constructor
98      *
99      */
100     public function __construct(array $_options = array())
101     {
102         $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP, new Tinebase_Config_Struct())->toArray();
103         
104         // merge _config and dbmail imap
105         $this->_config = array_merge($imapConfig['dbmail'], $this->_config);
106         
107         // set domain from imap config
108         $this->_config['domain'] = !empty($imapConfig['domain']) ? $imapConfig['domain'] : null;
109         
110         // _tablename = "dbmail_users"
111         $this->_userTable = $this->_config['prefix'] . $this->_config['userTable'];
112         
113         // connect to DB
114         $this->_getDb($this->_config);
115         
116         $columns = Tinebase_Db_Table::getTableDescriptionFromCache('dbmail_users', $this->_db);
117         if((isset($columns['tine20_userid']) || array_key_exists('tine20_userid', $columns)) && (isset($columns['tine20_clientid']) || array_key_exists('tine20_clientid', $columns))) {
118             $this->_hasTine20Userid = true;
119             $this->_propertyMapping['emailUserId'] = 'tine20_userid';
120             $this->_propertyMapping['emailGID']    = 'tine20_clientid';
121         }
122         
123         $this->_clientId = Tinebase_Application::getInstance()->getApplicationByName('Tinebase')->getId();
124         
125         $this->_config['emailGID'] = Tinebase_Application::getInstance()->getApplicationByName('Tinebase')->getId();
126     }
127     
128     /**
129      * delete user by id
130      *
131      * @param  Tinebase_Model_FullUser  $_user
132      */
133     public function inspectDeleteUser(Tinebase_Model_FullUser $_user)
134     {
135         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Delete Dbmail settings for user ' . $_user->accountLoginName);
136
137         if($this->_hasTine20Userid === true) {
138             $where = array(
139                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $_user->getId()),
140                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailGID'])    . ' = ?', $this->_config['emailGID'])
141             );
142         } else {
143             $where = array(
144                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $this->_convertToInt($_user->getId())),
145                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailGID'])    . ' = ?', $this->_convertToInt($this->_config['emailGID']))
146             );
147         }
148         
149         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " delete from {$this->_userTable} " . print_r($where, true));
150         
151         $this->_db->delete($this->_userTable, $where);
152     }
153     
154     /**
155      * inspect get user by property
156      * 
157      * @param  Tinebase_Model_User  $_user  the user object
158      */
159     public function inspectGetUserByProperty(Tinebase_Model_User $_user)
160     {
161         if (! $_user instanceof Tinebase_Model_FullUser) {
162             return;
163         }
164         
165         $userId = $_user->getId();
166         
167         $select = $this->_getSelect();
168         
169         if($this->_hasTine20Userid === true) {
170             $select->where($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?',   $userId);
171         } else {
172             $select->where($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?',   $this->_convertToInt($userId));
173         }
174         
175         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
176
177         // Perferom query - retrieve user from database
178         $stmt = $this->_db->query($select);
179         $queryResult = $stmt->fetch();
180         $stmt->closeCursor();
181                 
182         if (!$queryResult) {
183             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 'Dbmail config for user ' . $userId . ' not found!');
184             return;
185         }
186         
187         #if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($queryResult, TRUE));
188         
189         // convert data to Tinebase_Model_EmailUser       
190         $emailUser = $this->_rawDataToRecord($queryResult);
191         #if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($emailUser->toArray(), TRUE));
192         
193         // modify/correct user name
194         // set emailUsername to Tine accout login name and append domain for login purposes if set
195         $emailUser->emailUsername = $this->_appendDomain($_user->accountLoginName);
196
197         $_user->imapUser  = $emailUser;
198         $_user->emailUser = Tinebase_EmailUser::merge(clone $_user->imapUser, isset($_user->emailUser) ? $_user->emailUser : null);
199     }
200     
201     /**
202      * update/set email user password
203      * 
204      * @param  string  $_userId
205      * @param  string  $_password
206      * @param  bool    $_encrypt encrypt password
207      */
208     public function inspectSetPassword($_userId, $_password, $_encrypt = TRUE)
209     {
210         if (! $_encrypt && preg_match('/\{(.*)\}(.*)/', $_password, $matches)) {
211             // if password should not be encrypted but already contains encryption type, we separate pw and type 
212             $scheme = $matches[1];
213             $password = $matches[2];
214         } else {
215             $scheme = $this->_config['emailScheme'];
216             $password = ($_encrypt) ? Hash_Password::generate($scheme, $_password, false) : $_password;
217         }
218         
219         $values = array(
220             $this->_propertyMapping['emailScheme']   => $scheme,
221             $this->_propertyMapping['emailPassword'] => $password,
222         );
223         
224         if($this->_hasTine20Userid === true) {
225             $where = array(
226                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $_userId),
227                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailGID'])    . ' = ?', $this->_config['emailGID'])
228             );
229         } else {
230             $where = array(
231                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $this->_convertToInt($userId)),
232                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailGID'])    . ' = ?', $this->_convertToInt($this->_config['emailGID']))
233             );
234         }
235         
236         $this->_db->update($this->_userTable, $values, $where);
237     }
238     
239     /**
240      * adds email properties for a new user
241      * 
242      * @param  Tinebase_Model_FullUser  $_addedUser
243      * @param  Tinebase_Model_FullUser  $_newUserProperties
244      */
245     protected function _addUser(Tinebase_Model_FullUser $_addedUser, Tinebase_Model_FullUser $_newUserProperties)
246     {
247         if (! $_addedUser->accountEmailAddress) {
248             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ 
249                 . ' User ' . $_addedUser->accountDisplayName . ' has no email address defined. Skipping dbmail user creation.');
250             return;
251         }
252         
253         $imapSettings = $this->_recordToRawData($_addedUser, $_newUserProperties);
254         
255         $this->_removeNonDBValues($imapSettings);
256         
257         if (! $this->_checkOldUserRecord($imapSettings)) {
258             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
259                 . ' Adding Dbmail user ' . $imapSettings[$this->_propertyMapping['emailUsername']]);
260             
261             // generate random password if not set
262             if (empty($imapSettings[$this->_propertyMapping['emailPassword']])) {
263                 $imapSettings[$this->_propertyMapping['emailPassword']] = Hash_Password::generate($this->_config['emailScheme'], Tinebase_Record_Abstract::generateUID(), FALSE);
264             }
265             
266             $this->_db->insert($this->_userTable, $imapSettings);
267         }
268         
269         $this->inspectGetUserByProperty($_addedUser);
270     }
271     
272     /**
273      * remove some values that should not be written to dbmail DB
274      * 
275      * @param array $userdata
276      */
277     protected function _removeNonDBValues(&$userdata)
278     {
279         unset($userdata[$this->_propertyMapping['emailMailSize']]);
280         unset($userdata[$this->_propertyMapping['emailSieveSize']]);
281         unset($userdata[$this->_propertyMapping['emailLastLogin']]);
282     }
283     
284     /**
285      * check if old entry exists and update it
286      * 
287      * @param array $_userData
288      * @return boolean (TRUE if old record exists)
289      */
290     protected function _checkOldUserRecord($_userData)
291     {
292         $userIdProperty = $this->_propertyMapping['emailUsername'];
293         $where = $this->_db->quoteInto($this->_db->quoteIdentifier($userIdProperty) . ' = ?', $_userData[$userIdProperty]);
294         $select = $this->_db->select();
295         $select->from(array($this->_userTable => $this->_userTable), array($userIdProperty))
296             ->where($where)
297             ->limit(1);
298         
299         $stmt = $this->_db->query($select);
300         $queryResult = $stmt->fetch();
301         if ($queryResult) {
302             // preserve current pw
303             unset($_userData[$this->_propertyMapping['emailPassword']]);
304             
305             $this->_update($_userData, $where);
306             return TRUE;
307         }
308         
309         return FALSE;
310     }
311     
312     /**
313      * updates email properties for an existing user
314      * 
315      * @param  Tinebase_Model_FullUser  $_updatedUser
316      * @param  Tinebase_Model_FullUser  $_newUserProperties
317      */
318     protected function _updateUser(Tinebase_Model_FullUser $_updatedUser, Tinebase_Model_FullUser $_newUserProperties)
319     {
320         $imapSettings = $this->_recordToRawData($_updatedUser, $_newUserProperties);
321         
322         if($this->_hasTine20Userid === true) {
323             $where = array(
324                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $_updatedUser->getId()),
325                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailGID'])    . ' = ?', $this->_config['emailGID'])
326             );
327         } else {
328             $where = array(
329                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $this->_convertToInt($_updatedUser->getId())),
330                 $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailGID'])    . ' = ?', $this->_convertToInt($this->_config['emailGID']))
331             );
332         }
333
334         unset($imapSettings[$this->_propertyMapping['emailUserId']]);
335         $this->_removeNonDBValues($imapSettings);
336         
337         $this->_update($imapSettings, $where);
338         
339         $this->inspectGetUserByProperty($_updatedUser);
340     }
341     
342     /**
343      * update user in dbmail db
344      * 
345      * @param array $userData
346      * @param mixed $where
347      */
348     protected function _update($userData, $where)
349     {
350         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
351             . " Update user {$userData[$this->_propertyMapping['emailUsername']]} in {$this->_userTable}");
352         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
353             . " " . print_r($userData, TRUE));
354         
355         $this->_db->update($this->_userTable, $userData, $where);
356     }
357     
358     /**
359      * check if user exists already in dbmail user table
360      * 
361      * @param  Tinebase_Model_FullUser  $_user
362      */
363     protected function _userExists(Tinebase_Model_FullUser $_user)
364     {
365         $userId = $_user->getId();
366         
367         $select = $this->_getSelect();
368         
369         $select->where($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?',   ($this->_hasTine20Userid === true) ? $userId : $this->_convertToInt($userId));
370                   
371         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
372
373         // Perferom query - retrieve user from database
374         $stmt = $this->_db->query($select);
375         $queryResult = $stmt->fetch();
376         $stmt->closeCursor();
377                 
378         if (!$queryResult) {
379             return false;
380         }
381         
382         return true;
383     }
384     
385     /**
386      * converts raw data from adapter into a single record / do mapping
387      *
388      * @param  array                     $_data
389      * @return Tinebase_Model_EmailUser
390      */
391     protected function _rawDataToRecord(array $_rawdata)
392     {
393         $data = array();
394         
395         foreach ($_rawdata as $key => $value) {
396             $keyMapping = array_search($key, $this->_propertyMapping);
397             if ($keyMapping !== FALSE) {
398                 switch($keyMapping) {
399                     case 'emailPassword':
400                         // do nothing
401                         break;
402                         
403                     case 'emailMailQuota':
404                     case 'emailMailSize':
405                     case 'emailSieveQuota':
406                     case 'emailSieveSize':
407                         $data[$keyMapping] = Tinebase_Helper::convertToMegabytes($value);
408                         break;
409                         
410                     default: 
411                         $data[$keyMapping] = $value;
412                         break;
413                 }
414             }
415         }
416         
417         return new Tinebase_Model_EmailUser($data, true);
418     }
419     
420     /**
421      * returns array of raw Dbmail data
422      *
423      * @param  Tinebase_Model_EmailUser  $_user
424      * @param  Tinebase_Model_EmailUser  $_newUserProperties
425      * @return array
426      */
427     protected function _recordToRawData(Tinebase_Model_FullUser $_user, Tinebase_Model_FullUser $_newUserProperties)
428     {
429         $rawData = array();
430         
431         foreach ($_newUserProperties->imapUser as $key => $value) {
432             $property = (isset($this->_propertyMapping[$key]) || array_key_exists($key, $this->_propertyMapping)) ? $this->_propertyMapping[$key] : false;
433             if ($property && ! in_array($key, $this->_readOnlyFields)) {
434                 switch ($key) {
435                     case 'emailPassword':
436                         $rawData[$property] =  Hash_Password::generate($this->_config['emailScheme'], $value, false);
437                         $rawData[$this->_propertyMapping['emailScheme']]   = $this->_config['emailScheme'];
438                         break;
439                         
440                     case 'emailUserId':
441                     case 'emailGID':
442                     case 'emailUsername':
443                         // do nothing
444                         break;
445                         
446                     case 'emailMailQuota':
447                     case 'emailMailSize':
448                     case 'emailSieveQuota':
449                     case 'emailSieveSize':
450                         // convert to bytes
451                         $rawData[$property] = Tinebase_Helper::convertToBytes($value . 'M');
452                         break;
453                         
454                     default:
455                         $rawData[$property] = $value;
456                 }
457             }
458         }
459         
460         $rawData[$this->_propertyMapping['emailUserId']]   = $this->_hasTine20Userid === true ? $_user->getId() : $this->_convertToInt($_user->getId());
461         if($this->_hasTine20Userid === true) {
462             $rawData[$this->_propertyMapping['emailGID']]  = $this->_config['emailGID'];
463             $rawData['client_idnr']                        = $this->_convertToInt($this->_config['emailGID']);
464         } else {
465             $rawData[$this->_propertyMapping['emailGID']]  = $this->_convertToInt($this->_config['emailGID']);
466         }
467         $rawData[$this->_propertyMapping['emailUsername']] = $this->_appendDomain($_user->accountLoginName);
468         
469         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($rawData, TRUE));
470         
471         return $rawData;
472     }
473     
474     /**
475      * convert some string to absolute int with crc32
476      * 
477      * @param  $_string
478      * @return integer
479      */
480     protected function _convertToInt($_string)
481     {
482         return sprintf("%u", crc32($_string));
483     }
484     
485     /**
486      * create mailbox for user
487      * 
488      * @param  Tinebase_Model_EmailUser  $_emailUser
489      * @param  string                    $_mailboxName
490      * @return void
491      */
492     protected function _createMailbox(Tinebase_Model_EmailUser $_emailUser, $_mailboxName = 'INBOX')
493     {
494         $data = array(
495             'owner_idnr'    => $_emailUser->emailUID,
496             'name'          => $_mailboxName,
497             'seen_flag'     => 1,
498             'answered_flag' => 1,
499             'deleted_flag'  => 1,
500             'flagged_flag'  => 1,
501             'recent_flag'   => 1,
502             'draft_flag'    => 1,
503         );
504         
505         $this->_db->insert($this->_config['prefix'] . $this->_config['mailboxTable'], $data);
506     }
507     
508     /**
509      * get the basic select object to fetch records from the database
510      *  
511      * @param  array|string|Zend_Db_Expr  $_cols        columns to get, * per default
512      * @param  boolean                    $_getDeleted  get deleted records (if modlog is active)
513      * @return Zend_Db_Select
514      */
515     protected function _getSelect($_cols = '*', $_getDeleted = FALSE)
516     {
517         $select = $this->_db->select()
518             ->from($this->_userTable);
519
520         if($this->_hasTine20Userid === true) {
521             $select->where($this->_db->quoteIdentifier($this->_propertyMapping['emailGID']) . ' = ?', $this->_config['emailGID'])
522                    ->limit(1);
523         } else {
524             $select->where($this->_db->quoteIdentifier($this->_propertyMapping['emailGID']) . ' = ?', $this->_convertToInt($this->_config['emailGID']))
525                    ->limit(1);
526         }
527         
528         return $select;
529     }
530 }