Aleksander Machniak
2015-11-22 9f1f754daf4b57a0d0d3aea95d2321716d218cf5
commit | author | age
48e9c1 1 <?php
T 2
3 /**
4  *  Classes for managesieve operations (using PEAR::Net_Sieve)
5  *
6  * Copyright (C) 2008-2011, The Roundcube Dev Team
7  * Copyright (C) 2011, Kolab Systems AG
8  *
07c6c6 9  * This program is free software: you can redistribute it and/or modify
TB 10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
48e9c1 13  *
T 14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
07c6c6 19  * You should have received a copy of the GNU General Public License
TB 20  * along with this program. If not, see http://www.gnu.org/licenses/.
48e9c1 21  */
T 22
23 // Managesieve Protocol: RFC5804
24
25 class rcube_sieve
26 {
27     private $sieve;                 // Net_Sieve object
28     private $error = false;         // error flag
29     private $list = array();        // scripts list
30
31     public $script;                 // rcube_sieve_script object
32     public $current;                // name of currently loaded script
33     private $exts;                  // array of supported extensions
613f96 34
AM 35     const ERROR_CONNECTION = 1;
36     const ERROR_LOGIN      = 2;
37     const ERROR_NOT_EXISTS = 3;    // script not exists
38     const ERROR_INSTALL    = 4;    // script installation
39     const ERROR_ACTIVATE   = 5;    // script activation
40     const ERROR_DELETE     = 6;    // script deletion
41     const ERROR_INTERNAL   = 7;    // internal error
42     const ERROR_DEACTIVATE = 8;    // script activation
43     const ERROR_OTHER      = 255;  // other/unknown error
48e9c1 44
T 45
46     /**
47      * Object constructor
48      *
49      * @param string  Username (for managesieve login)
50      * @param string  Password (for managesieve login)
51      * @param string  Managesieve server hostname/address
52      * @param string  Managesieve server port number
53      * @param string  Managesieve authentication method 
54      * @param boolean Enable/disable TLS use
55      * @param array   Disabled extensions
56      * @param boolean Enable/disable debugging
57      * @param string  Proxy authentication identifier
58      * @param string  Proxy authentication password
b76f8e 59      * @param array   List of options to pass to stream_context_create().
48e9c1 60      */
T 61     public function __construct($username, $password='', $host='localhost', $port=2000,
62         $auth_type=null, $usetls=true, $disabled=array(), $debug=false,
b76f8e 63         $auth_cid=null, $auth_pw=null, $options=array())
48e9c1 64     {
T 65         $this->sieve = new Net_Sieve();
66
67         if ($debug) {
68             $this->sieve->setDebug(true, array($this, 'debug_handler'));
69         }
70
b59b72 71         $result = $this->sieve->connect($host, $port, $options, $usetls);
AM 72
73         if (is_a($result, 'PEAR_Error')) {
613f96 74             return $this->_set_error(self::ERROR_CONNECTION);
48e9c1 75         }
T 76
77         if (!empty($auth_cid)) {
78             $authz    = $username;
79             $username = $auth_cid;
6f41f0 80         }
JB 81         if (!empty($auth_pw)) {
48e9c1 82             $password = $auth_pw;
T 83         }
84
b59b72 85         $result = $this->sieve->login($username, $password, $auth_type ? strtoupper($auth_type) : null, $authz);
AM 86
87         if (is_a($result, 'PEAR_Error')) {
613f96 88             return $this->_set_error(self::ERROR_LOGIN);
48e9c1 89         }
T 90
b6fa7d 91         $this->exts = $this->get_extensions();
48e9c1 92
T 93         // disable features by config
94         if (!empty($disabled)) {
95             // we're working on lower-cased names
96             $disabled = array_map('strtolower', (array) $disabled);
97             foreach ($disabled as $ext) {
98                 if (($idx = array_search($ext, $this->exts)) !== false) {
99                     unset($this->exts[$idx]);
100                 }
101             }
102         }
103     }
104
105     public function __destruct() {
106         $this->sieve->disconnect();
107     }
108
109     /**
110      * Getter for error code
111      */
112     public function error()
113     {
9f1f75 114         return $this->error ?: false;
48e9c1 115     }
T 116
117     /**
118      * Saves current script into server
119      */
120     public function save($name = null)
121     {
b59b72 122         if (!$this->sieve) {
613f96 123             return $this->_set_error(self::ERROR_INTERNAL);
b59b72 124         }
48e9c1 125
b59b72 126         if (!$this->script) {
613f96 127             return $this->_set_error(self::ERROR_INTERNAL);
b59b72 128         }
48e9c1 129
b59b72 130         if (!$name) {
48e9c1 131             $name = $this->current;
b59b72 132         }
48e9c1 133
T 134         $script = $this->script->as_text();
135
b59b72 136         if (!$script) {
48e9c1 137             $script = '/* empty script */';
b59b72 138         }
48e9c1 139
b59b72 140         $result = $this->sieve->installScript($name, $script);
AM 141         if (is_a($result, 'PEAR_Error')) {
613f96 142             return $this->_set_error(self::ERROR_INSTALL);
b59b72 143         }
48e9c1 144
T 145         return true;
146     }
147
148     /**
149      * Saves text script into server
150      */
151     public function save_script($name, $content = null)
152     {
b59b72 153         if (!$this->sieve) {
613f96 154             return $this->_set_error(self::ERROR_INTERNAL);
b59b72 155         }
48e9c1 156
b59b72 157         if (!$content) {
48e9c1 158             $content = '/* empty script */';
b59b72 159         }
48e9c1 160
b59b72 161         $result = $this->sieve->installScript($name, $content);
AM 162
163         if (is_a($result, 'PEAR_Error')) {
613f96 164             return $this->_set_error(self::ERROR_INSTALL);
b59b72 165         }
48e9c1 166
T 167         return true;
168     }
169
170     /**
171      * Activates specified script
172      */
173     public function activate($name = null)
174     {
b59b72 175         if (!$this->sieve) {
613f96 176             return $this->_set_error(self::ERROR_INTERNAL);
b59b72 177         }
48e9c1 178
b59b72 179         if (!$name) {
48e9c1 180             $name = $this->current;
b59b72 181         }
48e9c1 182
b59b72 183         $result = $this->sieve->setActive($name);
AM 184
185         if (is_a($result, 'PEAR_Error')) {
613f96 186             return $this->_set_error(self::ERROR_ACTIVATE);
b59b72 187         }
48e9c1 188
T 189         return true;
190     }
191
192     /**
193      * De-activates specified script
194      */
195     public function deactivate()
196     {
b59b72 197         if (!$this->sieve) {
613f96 198             return $this->_set_error(self::ERROR_INTERNAL);
b59b72 199         }
48e9c1 200
b59b72 201         $result = $this->sieve->setActive('');
AM 202
203         if (is_a($result, 'PEAR_Error')) {
613f96 204             return $this->_set_error(self::ERROR_DEACTIVATE);
b59b72 205         }
48e9c1 206
T 207         return true;
208     }
209
210     /**
211      * Removes specified script
212      */
213     public function remove($name = null)
214     {
b59b72 215         if (!$this->sieve) {
613f96 216             return $this->_set_error(self::ERROR_INTERNAL);
b59b72 217         }
48e9c1 218
b59b72 219         if (!$name) {
48e9c1 220             $name = $this->current;
b59b72 221         }
48e9c1 222
T 223         // script must be deactivated first
b59b72 224         if ($name == $this->sieve->getActive()) {
AM 225             $result = $this->sieve->setActive('');
226
227             if (is_a($result, 'PEAR_Error')) {
613f96 228                 return $this->_set_error(self::ERROR_DELETE);
b59b72 229             }
AM 230         }
48e9c1 231
b59b72 232         $result = $this->sieve->removeScript($name);
AM 233
234         if (is_a($result, 'PEAR_Error')) {
613f96 235             return $this->_set_error(self::ERROR_DELETE);
b59b72 236         }
48e9c1 237
b59b72 238         if ($name == $this->current) {
48e9c1 239             $this->current = null;
b59b72 240         }
48e9c1 241
T 242         return true;
243     }
244
245     /**
246      * Gets list of supported by server Sieve extensions
247      */
248     public function get_extensions()
249     {
250         if ($this->exts)
251             return $this->exts;
252
253         if (!$this->sieve)
613f96 254             return $this->_set_error(self::ERROR_INTERNAL);
48e9c1 255
T 256         $ext = $this->sieve->getExtensions();
613f96 257
b59b72 258         if (is_a($ext, 'PEAR_Error')) {
613f96 259             return array();
AM 260         }
261
48e9c1 262         // we're working on lower-cased names
T 263         $ext = array_map('strtolower', (array) $ext);
264
265         if ($this->script) {
266             $supported = $this->script->get_extensions();
267             foreach ($ext as $idx => $ext_name)
268                 if (!in_array($ext_name, $supported))
269                     unset($ext[$idx]);
270         }
271
272         return array_values($ext);
273     }
274
275     /**
276      * Gets list of scripts from server
277      */
278     public function get_scripts()
279     {
280         if (!$this->list) {
281
282             if (!$this->sieve)
613f96 283                 return $this->_set_error(self::ERROR_INTERNAL);
48e9c1 284
T 285             $list = $this->sieve->listScripts();
286
b59b72 287             if (is_a($list, 'PEAR_Error')) {
613f96 288                 return $this->_set_error(self::ERROR_OTHER);
b59b72 289             }
48e9c1 290
T 291             $this->list = $list;
292         }
293
294         return $this->list;
295     }
296
297     /**
298      * Returns active script name
299      */
300     public function get_active()
301     {
302         if (!$this->sieve)
613f96 303             return $this->_set_error(self::ERROR_INTERNAL);
48e9c1 304
T 305         return $this->sieve->getActive();
306     }
307
308     /**
309      * Loads script by name
310      */
311     public function load($name)
312     {
313         if (!$this->sieve)
613f96 314             return $this->_set_error(self::ERROR_INTERNAL);
48e9c1 315
T 316         if ($this->current == $name)
317             return true;
318
319         $script = $this->sieve->getScript($name);
320
b59b72 321         if (is_a($script, 'PEAR_Error')) {
613f96 322             return $this->_set_error(self::ERROR_OTHER);
b59b72 323         }
48e9c1 324
T 325         // try to parse from Roundcube format
326         $this->script = $this->_parse($script);
327
328         $this->current = $name;
329
330         return true;
331     }
332
333     /**
334      * Loads script from text content
335      */
336     public function load_script($script)
337     {
338         if (!$this->sieve)
613f96 339             return $this->_set_error(self::ERROR_INTERNAL);
48e9c1 340
T 341         // try to parse from Roundcube format
342         $this->script = $this->_parse($script);
343     }
344
345     /**
346      * Creates rcube_sieve_script object from text script
347      */
348     private function _parse($txt)
349     {
350         // parse
351         $script = new rcube_sieve_script($txt, $this->exts);
352
353         // fix/convert to Roundcube format
354         if (!empty($script->content)) {
355             // replace all elsif with if+stop, we support only ifs
356             foreach ($script->content as $idx => $rule) {
357                 if (empty($rule['type']) || !preg_match('/^(if|elsif|else)$/', $rule['type'])) {
358                     continue;
359                 }
360
361                 $script->content[$idx]['type'] = 'if';
362
363                 // 'stop' not found?
364                 foreach ($rule['actions'] as $action) {
365                     if (preg_match('/^(stop|vacation)$/', $action['type'])) {
366                         continue 2;
367                     }
368                 }
04c856 369                 if (!empty($script->content[$idx+1]) && $script->content[$idx+1]['type'] != 'if') {
48e9c1 370                     $script->content[$idx]['actions'][] = array('type' => 'stop');
T 371                 }
372             }
373         }
374
375         return $script;
376     }
377
378     /**
379      * Gets specified script as text
380      */
381     public function get_script($name)
382     {
383         if (!$this->sieve)
613f96 384             return $this->_set_error(self::ERROR_INTERNAL);
48e9c1 385
T 386         $content = $this->sieve->getScript($name);
387
b59b72 388         if (is_a($content, 'PEAR_Error')) {
613f96 389             return $this->_set_error(self::ERROR_OTHER);
b59b72 390         }
48e9c1 391
T 392         return $content;
393     }
394
395     /**
396      * Creates empty script or copy of other script
397      */
398     public function copy($name, $copy)
399     {
400         if (!$this->sieve)
613f96 401             return $this->_set_error(self::ERROR_INTERNAL);
48e9c1 402
T 403         if ($copy) {
404             $content = $this->sieve->getScript($copy);
405
b59b72 406             if (is_a($content, 'PEAR_Error')) {
613f96 407                 return $this->_set_error(self::ERROR_OTHER);
b59b72 408             }
48e9c1 409         }
T 410
b59b72 411
48e9c1 412         return $this->save_script($name, $content);
T 413     }
414
415     private function _set_error($error)
416     {
417         $this->error = $error;
418         return false;
419     }
420
421     /**
422      * This is our own debug handler for connection
423      */
424     public function debug_handler(&$sieve, $message)
425     {
61be82 426         rcube::write_log('sieve', preg_replace('/\r\n$/', '', $message));
48e9c1 427     }
T 428 }