]> git.donarmstrong.com Git - roundcube.git/blob - plugins/managesieve/lib/rcube_sieve.php
3e52809137b320e5d8cbab18cfd270b59e153cf5
[roundcube.git] / plugins / managesieve / lib / rcube_sieve.php
1 <?php
2
3 /**
4   Classes for managesieve operations (using PEAR::Net_Sieve)
5
6   Author: Aleksander Machniak <alec@alec.pl>
7
8   $Id: rcube_sieve.php 5203 2011-09-12 06:44:56Z alec $
9
10 */
11
12 // Managesieve Protocol: RFC5804
13
14 define('SIEVE_ERROR_CONNECTION', 1);
15 define('SIEVE_ERROR_LOGIN', 2);
16 define('SIEVE_ERROR_NOT_EXISTS', 3);    // script not exists
17 define('SIEVE_ERROR_INSTALL', 4);       // script installation
18 define('SIEVE_ERROR_ACTIVATE', 5);      // script activation
19 define('SIEVE_ERROR_DELETE', 6);        // script deletion
20 define('SIEVE_ERROR_INTERNAL', 7);      // internal error
21 define('SIEVE_ERROR_DEACTIVATE', 8);    // script activation
22 define('SIEVE_ERROR_OTHER', 255);       // other/unknown error
23
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 $disabled;              // array of disabled extensions
34     private $exts;                  // array of supported extensions
35
36
37     /**
38      * Object constructor
39      *
40      * @param string  Username (for managesieve login)
41      * @param string  Password (for managesieve login)
42      * @param string  Managesieve server hostname/address
43      * @param string  Managesieve server port number
44      * @param string  Managesieve authentication method 
45      * @param boolean Enable/disable TLS use
46      * @param array   Disabled extensions
47      * @param boolean Enable/disable debugging
48      * @param string  Proxy authentication identifier
49      * @param string  Proxy authentication password
50      */
51     public function __construct($username, $password='', $host='localhost', $port=2000,
52         $auth_type=null, $usetls=true, $disabled=array(), $debug=false,
53         $auth_cid=null, $auth_pw=null)
54     {
55         $this->sieve = new Net_Sieve();
56
57         if ($debug) {
58             $this->sieve->setDebug(true, array($this, 'debug_handler'));
59         }
60
61         if (PEAR::isError($this->sieve->connect($host, $port, null, $usetls))) {
62             return $this->_set_error(SIEVE_ERROR_CONNECTION);
63         }
64
65         if (!empty($auth_cid)) {
66             $authz    = $username;
67             $username = $auth_cid;
68             $password = $auth_pw;
69         }
70
71         if (PEAR::isError($this->sieve->login($username, $password,
72             $auth_type ? strtoupper($auth_type) : null, $authz))
73         ) {
74             return $this->_set_error(SIEVE_ERROR_LOGIN);
75         }
76
77         $this->exts     = $this->get_extensions();
78         $this->disabled = $disabled;
79     }
80
81     public function __destruct() {
82         $this->sieve->disconnect();
83     }
84
85     /**
86      * Getter for error code
87      */
88     public function error()
89     {
90         return $this->error ? $this->error : false;
91     }
92
93     /**
94      * Saves current script into server
95      */
96     public function save($name = null)
97     {
98         if (!$this->sieve)
99             return $this->_set_error(SIEVE_ERROR_INTERNAL);
100
101         if (!$this->script)
102             return $this->_set_error(SIEVE_ERROR_INTERNAL);
103
104         if (!$name)
105             $name = $this->current;
106
107         $script = $this->script->as_text();
108
109         if (!$script)
110             $script = '/* empty script */';
111
112         if (PEAR::isError($this->sieve->installScript($name, $script)))
113             return $this->_set_error(SIEVE_ERROR_INSTALL);
114
115         return true;
116     }
117
118     /**
119      * Saves text script into server
120      */
121     public function save_script($name, $content = null)
122     {
123         if (!$this->sieve)
124             return $this->_set_error(SIEVE_ERROR_INTERNAL);
125
126         if (!$content)
127             $content = '/* empty script */';
128
129         if (PEAR::isError($this->sieve->installScript($name, $content)))
130             return $this->_set_error(SIEVE_ERROR_INSTALL);
131
132         return true;
133     }
134
135     /**
136      * Activates specified script
137      */
138     public function activate($name = null)
139     {
140         if (!$this->sieve)
141             return $this->_set_error(SIEVE_ERROR_INTERNAL);
142
143         if (!$name)
144             $name = $this->current;
145
146         if (PEAR::isError($this->sieve->setActive($name)))
147             return $this->_set_error(SIEVE_ERROR_ACTIVATE);
148
149         return true;
150     }
151
152     /**
153      * De-activates specified script
154      */
155     public function deactivate()
156     {
157         if (!$this->sieve)
158             return $this->_set_error(SIEVE_ERROR_INTERNAL);
159
160         if (PEAR::isError($this->sieve->setActive('')))
161             return $this->_set_error(SIEVE_ERROR_DEACTIVATE);
162
163         return true;
164     }
165
166     /**
167      * Removes specified script
168      */
169     public function remove($name = null)
170     {
171         if (!$this->sieve)
172             return $this->_set_error(SIEVE_ERROR_INTERNAL);
173
174         if (!$name)
175             $name = $this->current;
176
177         // script must be deactivated first
178         if ($name == $this->sieve->getActive())
179             if (PEAR::isError($this->sieve->setActive('')))
180                 return $this->_set_error(SIEVE_ERROR_DELETE);
181
182         if (PEAR::isError($this->sieve->removeScript($name)))
183             return $this->_set_error(SIEVE_ERROR_DELETE);
184
185         if ($name == $this->current)
186             $this->current = null;
187
188         return true;
189     }
190
191     /**
192      * Gets list of supported by server Sieve extensions
193      */
194     public function get_extensions()
195     {
196         if ($this->exts)
197             return $this->exts;
198     
199         if (!$this->sieve)
200             return $this->_set_error(SIEVE_ERROR_INTERNAL);
201
202         $ext = $this->sieve->getExtensions();
203         // we're working on lower-cased names
204         $ext = array_map('strtolower', (array) $ext);
205
206         if ($this->script) {
207             $supported = $this->script->get_extensions();
208             foreach ($ext as $idx => $ext_name)
209                 if (!in_array($ext_name, $supported))
210                     unset($ext[$idx]);
211         }
212
213         return array_values($ext);
214     }
215
216     /**
217      * Gets list of scripts from server
218      */
219     public function get_scripts()
220     {
221         if (!$this->list) {
222
223             if (!$this->sieve)
224                 return $this->_set_error(SIEVE_ERROR_INTERNAL);
225
226             $list = $this->sieve->listScripts();
227
228             if (PEAR::isError($list))
229                 return $this->_set_error(SIEVE_ERROR_OTHER);
230
231             $this->list = $list;
232         }
233
234         return $this->list;
235     }
236
237     /**
238      * Returns active script name
239      */
240     public function get_active()
241     {
242         if (!$this->sieve)
243             return $this->_set_error(SIEVE_ERROR_INTERNAL);
244
245         return $this->sieve->getActive();
246     }
247
248     /**
249      * Loads script by name
250      */
251     public function load($name)
252     {
253         if (!$this->sieve)
254             return $this->_set_error(SIEVE_ERROR_INTERNAL);
255
256         if ($this->current == $name)
257             return true;
258
259         $script = $this->sieve->getScript($name);
260
261         if (PEAR::isError($script))
262             return $this->_set_error(SIEVE_ERROR_OTHER);
263
264         // try to parse from Roundcube format
265         $this->script = $this->_parse($script);
266
267         $this->current = $name;
268
269         return true;
270     }
271
272     /**
273      * Loads script from text content
274      */
275     public function load_script($script)
276     {
277         if (!$this->sieve)
278             return $this->_set_error(SIEVE_ERROR_INTERNAL);
279
280         // try to parse from Roundcube format
281         $this->script = $this->_parse($script);
282     }
283
284     /**
285      * Creates rcube_sieve_script object from text script
286      */
287     private function _parse($txt)
288     {
289         // try to parse from Roundcube format
290         $script = new rcube_sieve_script($txt, $this->disabled, $this->exts);
291
292         // ... else try to import from different formats
293         if (empty($script->content)) {
294             $script = $this->_import_rules($txt);
295             $script = new rcube_sieve_script($script, $this->disabled, $this->exts);
296
297             // replace all elsif with if+stop, we support only ifs
298             foreach ($script->content as $idx => $rule) {
299                 // 'stop' not found?
300                 foreach ($rule['actions'] as $action) {
301                     if (preg_match('/^(stop|vacation)$/', $action['type'])) {
302                         continue 2;
303                     }
304                 }
305                 $script->content[$idx]['actions'][] = array('type' => 'stop');
306             }
307         }
308
309         return $script;
310     }
311
312     /**
313      * Gets specified script as text
314      */
315     public function get_script($name)
316     {
317         if (!$this->sieve)
318             return $this->_set_error(SIEVE_ERROR_INTERNAL);
319
320         $content = $this->sieve->getScript($name);
321
322         if (PEAR::isError($content))
323             return $this->_set_error(SIEVE_ERROR_OTHER);
324
325         return $content;
326     }
327
328     /**
329      * Creates empty script or copy of other script
330      */
331     public function copy($name, $copy)
332     {
333         if (!$this->sieve)
334             return $this->_set_error(SIEVE_ERROR_INTERNAL);
335
336         if ($copy) {
337             $content = $this->sieve->getScript($copy);
338
339             if (PEAR::isError($content))
340                 return $this->_set_error(SIEVE_ERROR_OTHER);
341         }
342
343         return $this->save_script($name, $content);
344     }
345
346     private function _import_rules($script)
347     {
348         $i = 0;
349         $name = array();
350
351         // Squirrelmail (Avelsieve)
352         if (preg_match('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\r?\n/', $script)) {
353             $tokens = preg_split('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE);
354             foreach ($tokens as $token) {
355                 if (preg_match('/^#START_SIEVE_RULE.*/', $token, $matches)) {
356                     $name[$i] = "unnamed rule ".($i+1);
357                     $content .= "# rule:[".$name[$i]."]\n";
358                 }
359                 elseif (isset($name[$i])) {
360                     // This preg_replace is added because I've found some Avelsieve scripts
361                     // with rules containing "if" here. I'm not sure it was working
362                     // before without this or not.
363                     $token = preg_replace('/^if\s+/', '', trim($token));
364                     $content .= "if $token\n";
365                     $i++;
366                 }
367             }
368         }
369         // Horde (INGO)
370         else if (preg_match('/(# .+)\r?\n/', $script)) {
371             $tokens = preg_split('/(# .+)\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE);
372             foreach($tokens as $token) {
373                 if (preg_match('/^# (.+)/', $token, $matches)) {
374                     $name[$i] = $matches[1];
375                     $content .= "# rule:[" . $name[$i] . "]\n";
376                 }
377                 elseif (isset($name[$i])) {
378                     $token = str_replace(":comparator \"i;ascii-casemap\" ", "", $token);
379                     $content .= $token . "\n";
380                     $i++;
381                 }
382             }
383         }
384
385         return $content;
386     }
387
388     private function _set_error($error)
389     {
390         $this->error = $error;
391         return false;
392     }
393
394     /**
395      * This is our own debug handler for connection
396      */
397     public function debug_handler(&$sieve, $message)
398     {
399         write_log('sieve', preg_replace('/\r\n$/', '', $message));
400     }
401 }