1 | <?php |
---|
2 | /** |
---|
3 | * sieve-php.lib.php |
---|
4 | * |
---|
5 | * $Id$ |
---|
6 | * |
---|
7 | * Copyright 2001-2003 Dan Ellis <danellis@rushmore.com> |
---|
8 | * |
---|
9 | * This program is released under the GNU Public License. See the enclosed |
---|
10 | * file COPYING for license information. If you did not receive this file, see |
---|
11 | * http://www.fsf.org/copyleft/gpl.html. |
---|
12 | * |
---|
13 | * You should have received a copy of the GNU Public License along with this |
---|
14 | * package; if not, write to the Free Software Foundation, Inc., 59 Temple |
---|
15 | * Place - Suite 330, Boston, MA 02111-1307, USA. |
---|
16 | * |
---|
17 | * See CHANGES for updates since last release |
---|
18 | * |
---|
19 | * @author Dan Ellis |
---|
20 | * @package sieve-php |
---|
21 | * @copyright Copyright 2002-2003, Dan Ellis, All Rights Reserved. |
---|
22 | * @version 0.1.0 |
---|
23 | */ |
---|
24 | |
---|
25 | /** |
---|
26 | * Constants |
---|
27 | */ |
---|
28 | define ("F_NO", 0); |
---|
29 | define ("F_OK", 1); |
---|
30 | define ("F_DATA", 2); |
---|
31 | define ("F_HEAD", 3); |
---|
32 | |
---|
33 | define ("EC_NOT_LOGGED_IN", 0); |
---|
34 | define ("EC_QUOTA", 10); |
---|
35 | define ("EC_NOSCRIPTS", 20); |
---|
36 | define ("EC_UNKNOWN", 255); |
---|
37 | |
---|
38 | /** |
---|
39 | * SIEVE class - A Class that implements MANAGESIEVE in PHP4|5. |
---|
40 | * |
---|
41 | * This program provides a handy interface into the Cyrus timsieved server |
---|
42 | * under php4. It is tested with Sieve server included in Cyrus 2.0, but it |
---|
43 | * has been upgraded (not tested) to work with older Sieve server versions. |
---|
44 | * |
---|
45 | * All functions will return either true or false and will fill in |
---|
46 | * $sieve->error with a defined error code like EC_QUOTA, raw server errors in |
---|
47 | * $sieve->error_raw, and successful responses in $sieve->responses. |
---|
48 | * |
---|
49 | * NOTE: a major change since version (0.0.5) is the inclusion of a standard |
---|
50 | * method to retrieve server responses. All functions will return either true |
---|
51 | * or false and will fill in $sieve->error with a defined error code like |
---|
52 | * EC_QUOTA, raw server errors in $sieve->error_raw, and successful responses |
---|
53 | * in $sieve->responses. |
---|
54 | * |
---|
55 | * Usage is pretty simple. The basics is login, do what you need and logout. |
---|
56 | * There are two sample files (which suck) test.php and testsieve.php. |
---|
57 | * test.php allows you to create/delete/view scripts and testsieve.php is a |
---|
58 | * very basic sieve server test. |
---|
59 | * |
---|
60 | * Please let us know of any bugs, problems or ideas at sieve-php development |
---|
61 | * list: sieve-php-devel@lists.sourceforge.net. A web interface to subscribe |
---|
62 | * to this list is available at: |
---|
63 | * https://lists.sourceforge.net/mailman/listinfo/sieve-php-devel |
---|
64 | * |
---|
65 | * @author Dan Ellis |
---|
66 | * @example simple_example.php A simple example that shows usage of sieve-php |
---|
67 | * class. |
---|
68 | * @example vacationset-sieve.php A more elaborate example of vacation script |
---|
69 | * handling. |
---|
70 | * @version 0.1.0 |
---|
71 | * @package sieve-php |
---|
72 | * @todo Maybe add the NOOP function. |
---|
73 | * @todo Have timing mechanism when port problems arise. |
---|
74 | * @todo Provide better error diagnostics. |
---|
75 | */ |
---|
76 | class sieve { |
---|
77 | var $host; |
---|
78 | var $port; |
---|
79 | var $user; |
---|
80 | var $pass; |
---|
81 | /** |
---|
82 | * a comma seperated list of allowed auth types, in order of preference |
---|
83 | */ |
---|
84 | var $auth_types; |
---|
85 | /** |
---|
86 | * type of authentication attempted |
---|
87 | */ |
---|
88 | var $auth_in_use; |
---|
89 | |
---|
90 | var $line; |
---|
91 | var $fp; |
---|
92 | var $retval; |
---|
93 | var $tmpfile; |
---|
94 | var $fh; |
---|
95 | var $len; |
---|
96 | var $script; |
---|
97 | |
---|
98 | var $loggedin; |
---|
99 | var $capabilities; |
---|
100 | var $error; |
---|
101 | var $error_raw; |
---|
102 | var $responses; |
---|
103 | |
---|
104 | //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN) |
---|
105 | //so we can decide how to handle certain errors?!? |
---|
106 | |
---|
107 | /** |
---|
108 | * get response |
---|
109 | */ |
---|
110 | function get_response() |
---|
111 | { |
---|
112 | if($this->loggedin == false or feof($this->fp)){ |
---|
113 | $this->error = EC_NOT_LOGGED_IN; |
---|
114 | $this->error_raw = "You are not logged in."; |
---|
115 | return false; |
---|
116 | } |
---|
117 | |
---|
118 | unset($this->response); |
---|
119 | unset($this->error); |
---|
120 | unset($this->error_raw); |
---|
121 | |
---|
122 | $this->line=fgets($this->fp,1024); |
---|
123 | $this->token = split(" ", $this->line, 2); |
---|
124 | |
---|
125 | if($this->token[0] == "NO"){ |
---|
126 | /* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of: |
---|
127 | NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */ |
---|
128 | $this->x = 0; |
---|
129 | list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3); |
---|
130 | if($this->mtoken[0] == "{"){ |
---|
131 | while($this->mtoken[$this->x] != "}" or $this->err_len < 1){ |
---|
132 | $this->err_len = substr($this->mtoken, 1, $this->x); |
---|
133 | $this->x++; |
---|
134 | } |
---|
135 | //print "<br>Trying to receive $this->err_len bytes for result<br>"; |
---|
136 | $this->line = fgets($this->fp,$this->err_len); |
---|
137 | $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's |
---|
138 | $this->err_recv = strlen($this->line); |
---|
139 | |
---|
140 | while($this->err_recv < $this->err_len){ |
---|
141 | //print "<br>Trying to receive ".($this->err_len-$this->err_recv)." bytes for result<br>"; |
---|
142 | $this->line = fgets($this->fp, ($this->err_len-$this->err_recv)); |
---|
143 | $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's |
---|
144 | $this->err_recv += strlen($this->line); |
---|
145 | } /* end while */ |
---|
146 | $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug... |
---|
147 | $this->error=EC_UNKNOWN; |
---|
148 | |
---|
149 | } /* end if */ |
---|
150 | elseif($this->mtoken[0] == "("){ |
---|
151 | switch($this->mtoken){ |
---|
152 | case "(\"QUOTA\")": |
---|
153 | $this->error = EC_QUOTA; |
---|
154 | $this->error_raw=$this->rtoken; |
---|
155 | break; |
---|
156 | default: |
---|
157 | $this->error = EC_UNKNOWN; |
---|
158 | $this->error_raw=$this->rtoken; |
---|
159 | break; |
---|
160 | } /* end switch */ |
---|
161 | } /* end elseif */ |
---|
162 | else{ |
---|
163 | $this->error = EC_UNKNOWN; |
---|
164 | $this->error_raw = $this->line; |
---|
165 | } |
---|
166 | return false; |
---|
167 | |
---|
168 | } /* end if */ |
---|
169 | elseif(substr($this->token[0],0,2) == "OK"){ |
---|
170 | return true; |
---|
171 | } /* end elseif */ |
---|
172 | elseif($this->token[0][0] == "{"){ |
---|
173 | |
---|
174 | /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */ |
---|
175 | |
---|
176 | /* the first line is the len field {xx}, which we don't care about at this point */ |
---|
177 | $this->line = fgets($this->fp,1024); |
---|
178 | while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ |
---|
179 | $this->response[]=$this->line; |
---|
180 | $this->line = fgets($this->fp, 1024); |
---|
181 | } |
---|
182 | if(substr($this->line,0,2) == "OK") |
---|
183 | return true; |
---|
184 | else |
---|
185 | return false; |
---|
186 | } /* end elseif */ |
---|
187 | elseif($this->token[0][0] == "\""){ |
---|
188 | |
---|
189 | /* I'm going under the _assumption_ that the only function that will get here is the listscripts(). |
---|
190 | I could very well be mistaken here, if I am, this part needs some rework */ |
---|
191 | |
---|
192 | $this->found_script=false; |
---|
193 | |
---|
194 | while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ |
---|
195 | $this->found_script=true; |
---|
196 | list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2); |
---|
197 | //hmmm, a bug in php, if there is no space on explode line, a warning is generated... |
---|
198 | |
---|
199 | if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){ |
---|
200 | $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1); |
---|
201 | } |
---|
202 | else |
---|
203 | $this->response[] = substr(rtrim($this->ltoken),1,-1); |
---|
204 | $this->line = fgets($this->fp, 1024); |
---|
205 | } /* end while */ |
---|
206 | |
---|
207 | return true; |
---|
208 | |
---|
209 | } /* end elseif */ |
---|
210 | else{ |
---|
211 | $this->error = EC_UNKNOWN; |
---|
212 | $this->error_raw = $this->line; |
---|
213 | print '<b><i>UNKNOWN ERROR (Please report this line to <a |
---|
214 | href="mailto:sieve-php-devel@lists.sourceforge.net">sieve-php-devel |
---|
215 | Mailing List</a> to include in future releases): |
---|
216 | '.$this->line.'</i></b><br>'; |
---|
217 | |
---|
218 | return false; |
---|
219 | } /* end else */ |
---|
220 | } /* end get_response() */ |
---|
221 | |
---|
222 | /** |
---|
223 | * Initialization of the SIEVE class. |
---|
224 | * |
---|
225 | * It will return |
---|
226 | * false if it fails, true if all is well. This also loads some arrays up |
---|
227 | * with some handy information: |
---|
228 | * |
---|
229 | * @param $host string hostname to connect to. Usually the IMAP server where |
---|
230 | * a SIEVE daemon, such as timsieved, is listening. |
---|
231 | * |
---|
232 | * @param $port string Numeric port to connect to. SIEVE daemons usually |
---|
233 | * listen to port 2000. |
---|
234 | * |
---|
235 | * @param $user string is a super-user or proxy-user that has ACL rights to |
---|
236 | * login on behalf of the $auth. |
---|
237 | * |
---|
238 | * @param $pass string password to use for authentication |
---|
239 | * |
---|
240 | * @param $auth string is the authorized user identity for which the SIEVE |
---|
241 | * scripts will be managed. |
---|
242 | * |
---|
243 | * @param $auth_types string a string containing all the allowed |
---|
244 | * authentication types allowed in order of preference, seperated by spaces. |
---|
245 | * (ex. "PLAIN DIGEST-MD5 CRAM-MD5" The method the library will try first |
---|
246 | * is PLAIN.) The default for this value is PLAIN. |
---|
247 | * |
---|
248 | * Note: $user, if included, is the account name (and $pass will be the |
---|
249 | * password) of an administrator account that can act on behalf of the user. |
---|
250 | * If you are using Cyrus, you must make sure that the admin account has |
---|
251 | * rights to admin the user. This is to allow admins to edit/view users |
---|
252 | * scripts without having to know the user's password. Very handy. |
---|
253 | */ |
---|
254 | function sieve($host, $port, $user, $pass, $auth="", $auth_types='PLAIN') { |
---|
255 | $this->host=$host; |
---|
256 | $this->port=$port; |
---|
257 | $this->user=$user; |
---|
258 | $this->pass=$pass; |
---|
259 | if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */ |
---|
260 | $this->auth = $this->user; |
---|
261 | else |
---|
262 | $this->auth = $auth; |
---|
263 | $this->auth_types=$auth_types; /* Allowed authentication types */ |
---|
264 | $this->fp=0; |
---|
265 | $this->line=""; |
---|
266 | $this->retval=""; |
---|
267 | $this->tmpfile=""; |
---|
268 | $this->fh=0; |
---|
269 | $this->len=0; |
---|
270 | $this->capabilities=""; |
---|
271 | $this->loggedin=false; |
---|
272 | $this->error= ""; |
---|
273 | $this->error_raw=""; |
---|
274 | } |
---|
275 | |
---|
276 | /** |
---|
277 | * Tokenize a line of input by quote marks and return them as an array |
---|
278 | * |
---|
279 | * @param $string string Input line to parse for quotes |
---|
280 | * @return array Array of broken by quotes parts of original string |
---|
281 | */ |
---|
282 | function parse_for_quotes($string) { |
---|
283 | |
---|
284 | $start = -1; |
---|
285 | $index = 0; |
---|
286 | |
---|
287 | for($ptr = 0; $ptr < strlen($string); $ptr++){ |
---|
288 | if($string[$ptr] == '"' and $string[$ptr] != '\\'){ |
---|
289 | if($start == -1){ |
---|
290 | $start = $ptr; |
---|
291 | } /* end if */ |
---|
292 | else{ |
---|
293 | $token[$index++] = substr($string, $start + 1, $ptr - $start - 1); |
---|
294 | $found = true; |
---|
295 | $start = -1; |
---|
296 | } /* end else */ |
---|
297 | |
---|
298 | } /* end if */ |
---|
299 | |
---|
300 | } /* end for */ |
---|
301 | |
---|
302 | if(isset($token)) |
---|
303 | return $token; |
---|
304 | else |
---|
305 | return false; |
---|
306 | } /* end function */ |
---|
307 | |
---|
308 | /** |
---|
309 | * Parser for status responses. |
---|
310 | * |
---|
311 | * This should probably be replaced by a smarter parser. |
---|
312 | * |
---|
313 | * @param $string string Input that contains status responses. |
---|
314 | * @todo remove this function and dependencies |
---|
315 | */ |
---|
316 | function status($string) { |
---|
317 | |
---|
318 | /* Need to remove this and all dependencies from the class */ |
---|
319 | |
---|
320 | switch (substr($string, 0,2)){ |
---|
321 | case "NO": |
---|
322 | return F_NO; //there should be some function to extract the error code from this line |
---|
323 | //NO ("quota") "You are oly allowed x number of scripts" |
---|
324 | break; |
---|
325 | case "OK": |
---|
326 | return F_OK; |
---|
327 | break; |
---|
328 | default: |
---|
329 | switch ($string[0]){ |
---|
330 | case "{": |
---|
331 | //do parse here for curly braces - maybe modify |
---|
332 | //parse_for_quotes to handle any parse delimiter? |
---|
333 | return F_HEAD; |
---|
334 | break; |
---|
335 | default: |
---|
336 | return F_DATA; |
---|
337 | break; |
---|
338 | } |
---|
339 | } |
---|
340 | } |
---|
341 | |
---|
342 | /** |
---|
343 | * Attemp to log in to the sieve server. |
---|
344 | * |
---|
345 | * It will return false if it fails, true if all is well. This also loads |
---|
346 | * some arrays up with some handy information: |
---|
347 | * |
---|
348 | * capabilities["implementation"] contains the sieve version information |
---|
349 | * |
---|
350 | * capabilities["auth"] contains the supported authentication modes by the |
---|
351 | * SIEVE server. |
---|
352 | * |
---|
353 | * capabilities["modules"] contains the built in modules like "reject", |
---|
354 | * "redirect", etc. |
---|
355 | * |
---|
356 | * capabilities["starttls"] , if is set and equal to true, will show that the |
---|
357 | * server supports the STARTTLS extension. |
---|
358 | * |
---|
359 | * capabilities["unknown"] contains miscellaneous/extraneous header info sieve |
---|
360 | * may have sent |
---|
361 | * |
---|
362 | * @return boolean |
---|
363 | */ |
---|
364 | function sieve_login() { |
---|
365 | $this->fp=fsockopen($this->host,$this->port); |
---|
366 | if($this->fp == false) |
---|
367 | return false; |
---|
368 | |
---|
369 | $this->line=fgets($this->fp,1024); |
---|
370 | //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard |
---|
371 | //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}" |
---|
372 | //So, if we see IMLEMENTATION in the first line, then we are done. |
---|
373 | |
---|
374 | if(ereg("IMPLEMENTATION",$this->line)) |
---|
375 | { |
---|
376 | //we're on the Cyrus V2 sieve server |
---|
377 | while(sieve::status($this->line) == F_DATA){ |
---|
378 | |
---|
379 | $this->item = sieve::parse_for_quotes($this->line); |
---|
380 | |
---|
381 | if(strcmp($this->item[0], "IMPLEMENTATION") == 0) |
---|
382 | $this->capabilities["implementation"] = $this->item[1]; |
---|
383 | |
---|
384 | elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){ |
---|
385 | |
---|
386 | if(strcmp($this->item[0], "SIEVE") == 0) |
---|
387 | $this->cap_type="modules"; |
---|
388 | else |
---|
389 | $this->cap_type="auth"; |
---|
390 | |
---|
391 | $this->modules = split(" ", $this->item[1]); |
---|
392 | if(is_array($this->modules)){ |
---|
393 | foreach($this->modules as $this->module) |
---|
394 | $this->capabilities[$this->cap_type][$this->module]=true; |
---|
395 | } /* end if */ |
---|
396 | elseif(is_string($this->modules)) |
---|
397 | $this->capabilites[$this->cap_type][$this->modules]=true; |
---|
398 | } |
---|
399 | elseif(strcmp($this->item[0], "STARTTLS") == 0) { |
---|
400 | $this->capabilities['starttls'] = true; |
---|
401 | |
---|
402 | } |
---|
403 | else{ |
---|
404 | $this->capabilities["unknown"][]=$this->line; |
---|
405 | } |
---|
406 | $this->line=fgets($this->fp,1024); |
---|
407 | }// end while |
---|
408 | } |
---|
409 | else |
---|
410 | { |
---|
411 | //we're on the older Cyrus V1. server |
---|
412 | //this version does not support module reporting. We only have auth types. |
---|
413 | $this->cap_type="auth"; |
---|
414 | |
---|
415 | //break apart at the "Cyrus timsieve...." "SASL={......}" |
---|
416 | $this->item = sieve::parse_for_quotes($this->line); |
---|
417 | |
---|
418 | $this->capabilities["implementation"] = $this->item[0]; |
---|
419 | |
---|
420 | //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz} |
---|
421 | $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1); |
---|
422 | |
---|
423 | //then split again at the ", " stuff. |
---|
424 | $this->modules = split($this->modules, ", "); |
---|
425 | |
---|
426 | //fill up our $this->modules property |
---|
427 | if(is_array($this->modules)){ |
---|
428 | foreach($this->modules as $this->module) |
---|
429 | $this->capabilities[$this->cap_type][$this->module]=true; |
---|
430 | } /* end if */ |
---|
431 | elseif(is_string($this->modules)) |
---|
432 | $this->capabilites[$this->cap_type][$this->module]=true; |
---|
433 | } |
---|
434 | |
---|
435 | |
---|
436 | |
---|
437 | |
---|
438 | if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes? |
---|
439 | $this->error=EC_UNKNOWN; |
---|
440 | $this->error_raw = "Server not allowing connections."; |
---|
441 | return false; |
---|
442 | } |
---|
443 | |
---|
444 | /* decision login to decide what type of authentication to use... */ |
---|
445 | |
---|
446 | |
---|
447 | /* Loop through each allowed authentication type and see if the server allows the type */ |
---|
448 | foreach(explode(" ", $this->auth_types) as $auth_type) |
---|
449 | { |
---|
450 | if ($this->capabilities["auth"][$auth_type]) |
---|
451 | { |
---|
452 | /* We found an auth type that is allowed. */ |
---|
453 | $this->auth_in_use = $auth_type; |
---|
454 | } |
---|
455 | } |
---|
456 | |
---|
457 | /* call our authentication program */ |
---|
458 | |
---|
459 | return sieve::authenticate(); |
---|
460 | |
---|
461 | } |
---|
462 | |
---|
463 | /** |
---|
464 | * Log out of the sieve server. |
---|
465 | * |
---|
466 | * @return boolean Always returns true at this point. |
---|
467 | */ |
---|
468 | function sieve_logout() { |
---|
469 | if($this->loggedin==false) |
---|
470 | return false; |
---|
471 | |
---|
472 | fputs($this->fp,"LOGOUT\r\n"); |
---|
473 | fclose($this->fp); |
---|
474 | $this->loggedin=false; |
---|
475 | return true; |
---|
476 | } |
---|
477 | |
---|
478 | /** |
---|
479 | * Send the script contained in $script to the server. |
---|
480 | * |
---|
481 | * It will return any error results it finds (in $sieve->error and |
---|
482 | * $sieve->error_raw), and return true if it is successfully sent. The |
---|
483 | * function does _not_ automatically make the script the active script. |
---|
484 | * |
---|
485 | * @param $scriptname string The name of the SIEVE script. |
---|
486 | * @param $script The script to be uploaded. |
---|
487 | * @return boolean Returns true if script has been successfully uploaded. |
---|
488 | */ |
---|
489 | function sieve_sendscript($scriptname, $script) { |
---|
490 | if($this->loggedin==false) |
---|
491 | return false; |
---|
492 | $this->script=stripslashes($script); |
---|
493 | $len=strlen($this->script); |
---|
494 | |
---|
495 | //fputs($this->fp, "PUTSCRIPT \"$scriptname\" \{$len+}\r\n"); |
---|
496 | |
---|
497 | fputs($this->fp, 'PUTSCRIPT "' . $scriptname . '" {' . $len . '+}' . "\r\n"); |
---|
498 | fputs($this->fp, "$this->script\r\n"); |
---|
499 | |
---|
500 | return sieve::get_response(); |
---|
501 | |
---|
502 | } |
---|
503 | |
---|
504 | /** |
---|
505 | * Check if there is enough space for a script to be uploaded. |
---|
506 | * |
---|
507 | * This function returns true or false based on whether the sieve server will |
---|
508 | * allow your script to be sent and your quota has not been exceeded. This |
---|
509 | * function does not currently work due to a believed bug in timsieved. It |
---|
510 | * could be my code too. |
---|
511 | * |
---|
512 | * It appears the timsieved does not honor the NUMBER type. see lex.c in |
---|
513 | * timsieved src. don't expect this function to work yet. I might have |
---|
514 | * messed something up here, too. |
---|
515 | * |
---|
516 | * @param $scriptname string The name of the SIEVE script. |
---|
517 | * @param $scriptsize integer The size of the SIEVE script. |
---|
518 | * @return boolean |
---|
519 | * @todo Does not work; bug fix and test. |
---|
520 | */ |
---|
521 | function sieve_havespace($scriptname, $scriptsize) { |
---|
522 | if($this->loggedin==false) |
---|
523 | return false; |
---|
524 | fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n"); |
---|
525 | return sieve::get_response(); |
---|
526 | } |
---|
527 | |
---|
528 | /** |
---|
529 | * Set the script active on the sieve server. |
---|
530 | * |
---|
531 | * @param $scriptname string The name of the SIEVE script. |
---|
532 | * @return boolean |
---|
533 | */ |
---|
534 | function sieve_setactivescript($scriptname) { |
---|
535 | if($this->loggedin==false) |
---|
536 | return false; |
---|
537 | |
---|
538 | fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n"); |
---|
539 | return sieve::get_response(); |
---|
540 | |
---|
541 | } |
---|
542 | |
---|
543 | /** |
---|
544 | * Return the contents of the requested script. |
---|
545 | * |
---|
546 | * If you want to display the script, you will need to change all CrLf to |
---|
547 | * '.'. |
---|
548 | * |
---|
549 | * @param $scriptname string The name of the SIEVE script. |
---|
550 | * @return arr SIEVE script data. |
---|
551 | */ |
---|
552 | function sieve_getscript($scriptname) { |
---|
553 | unset($this->script); |
---|
554 | if($this->loggedin==false) |
---|
555 | return false; |
---|
556 | |
---|
557 | fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n"); |
---|
558 | return sieve::get_response(); |
---|
559 | } |
---|
560 | |
---|
561 | /** |
---|
562 | * Attempt to delete the script requested. |
---|
563 | * |
---|
564 | * If the script is currently active, the server will not have any active |
---|
565 | * script after the deletion. |
---|
566 | * |
---|
567 | * @param $scriptname string The name of the SIEVE script. |
---|
568 | * @return mixed |
---|
569 | */ |
---|
570 | function sieve_deletescript($scriptname) { |
---|
571 | if($this->loggedin==false) |
---|
572 | return false; |
---|
573 | |
---|
574 | fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n"); |
---|
575 | |
---|
576 | return sieve::get_response(); |
---|
577 | } |
---|
578 | |
---|
579 | |
---|
580 | /** |
---|
581 | * List available scripts on the SIEVE server. |
---|
582 | * |
---|
583 | * This function returns true or false. $sieve->response will be filled |
---|
584 | * with the names of the scripts found. If a script is active, the |
---|
585 | * $sieve->response["ACTIVE"] will contain the name of the active script. |
---|
586 | * |
---|
587 | * @return boolean |
---|
588 | */ |
---|
589 | function sieve_listscripts() { |
---|
590 | fputs($this->fp, "LISTSCRIPTS\r\n"); |
---|
591 | sieve::get_response(); //should always return true, even if there are no scripts... |
---|
592 | if(isset($this->found_script) and $this->found_script) |
---|
593 | return true; |
---|
594 | else{ |
---|
595 | $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found... |
---|
596 | $this->error_raw="No scripts found for this account."; |
---|
597 | return false; |
---|
598 | } |
---|
599 | } |
---|
600 | |
---|
601 | |
---|
602 | /** |
---|
603 | * Check availability of connection to the SIEVE server. |
---|
604 | * |
---|
605 | * This function returns true or false based on whether the connection to the |
---|
606 | * sieve server is still alive. |
---|
607 | * |
---|
608 | * @return boolean |
---|
609 | */ |
---|
610 | function sieve_alive() { |
---|
611 | if(!isset($this->fp) or $this->fp==0){ |
---|
612 | $this->error = EC_NOT_LOGGED_IN; |
---|
613 | return false; |
---|
614 | } |
---|
615 | elseif(feof($this->fp)){ |
---|
616 | $this->error = EC_NOT_LOGGED_IN; |
---|
617 | return false; |
---|
618 | } |
---|
619 | else |
---|
620 | return true; |
---|
621 | } |
---|
622 | |
---|
623 | /** |
---|
624 | * Perform SASL authentication to SIEVE server. |
---|
625 | * |
---|
626 | * Attempts to authenticate to SIEVE, using some SASL authentication method |
---|
627 | * such as PLAIN or DIGEST-MD5. |
---|
628 | * |
---|
629 | */ |
---|
630 | function authenticate() { |
---|
631 | switch ($this->auth_in_use) { |
---|
632 | |
---|
633 | case "PLAIN": |
---|
634 | $auth=base64_encode("$this->user\0$this->auth\0$this->pass"); |
---|
635 | $this->len=strlen($auth); |
---|
636 | |
---|
637 | fputs($this->fp, 'AUTHENTICATE "PLAIN" {' . $this->len . '+}' . "\r\n"); |
---|
638 | fputs($this->fp, "$auth\r\n"); |
---|
639 | |
---|
640 | $this->line=fgets($this->fp,1024); |
---|
641 | while(sieve::status($this->line) == F_DATA) |
---|
642 | $this->line=fgets($this->fp,1024); |
---|
643 | |
---|
644 | if(sieve::status($this->line) == F_NO) |
---|
645 | return false; |
---|
646 | $this->loggedin=true; |
---|
647 | return true; |
---|
648 | break; |
---|
649 | |
---|
650 | case "DIGEST-MD5": |
---|
651 | // SASL DIGEST-MD5 support works with timsieved 1.1.0 |
---|
652 | // follows rfc2831 for generating the $response to $challenge |
---|
653 | fputs($this->fp, "AUTHENTICATE \"DIGEST-MD5\"\r\n"); |
---|
654 | // $clen is length of server challenge, we ignore it. |
---|
655 | $clen = fgets($this->fp, 1024); |
---|
656 | // read for 2048, rfc2831 max length allowed |
---|
657 | $challenge = fgets($this->fp, 2048); |
---|
658 | // vars used when building $response_value and $response |
---|
659 | $cnonce = base64_encode(bin2hex(hmac_md5(microtime()))); |
---|
660 | $ncount = "00000001"; |
---|
661 | $qop_value = "auth"; |
---|
662 | $digest_uri_value = "sieve/$this->host"; |
---|
663 | // decode the challenge string |
---|
664 | $result = decode_challenge($challenge); |
---|
665 | // verify server supports qop=auth |
---|
666 | $qop = explode(",",$result['qop']); |
---|
667 | if (!in_array($qop_value, $qop)) { |
---|
668 | // rfc2831: client MUST fail if no qop methods supported |
---|
669 | return false; |
---|
670 | } |
---|
671 | // build the $response_value |
---|
672 | $string_a1 = utf8_encode($this->user).":"; |
---|
673 | $string_a1 .= utf8_encode($result['realm']).":"; |
---|
674 | $string_a1 .= utf8_encode($this->pass); |
---|
675 | $string_a1 = hmac_md5($string_a1); |
---|
676 | $A1 = $string_a1.":".$result['nonce'].":".$cnonce.":".utf8_encode($this->auth); |
---|
677 | $A1 = bin2hex(hmac_md5($A1)); |
---|
678 | $A2 = bin2hex(hmac_md5("AUTHENTICATE:$digest_uri_value")); |
---|
679 | $string_response = $result['nonce'].":".$ncount.":".$cnonce.":".$qop_value; |
---|
680 | $response_value = bin2hex(hmac_md5($A1.":".$string_response.":".$A2)); |
---|
681 | // build the challenge $response |
---|
682 | $reply = "charset=utf-8,username=\"".$this->user."\",realm=\"".$result['realm']."\","; |
---|
683 | $reply .= "nonce=\"".$result['nonce']."\",nc=$ncount,cnonce=\"$cnonce\","; |
---|
684 | $reply .= "digest-uri=\"$digest_uri_value\",response=$response_value,"; |
---|
685 | $reply .= "qop=$qop_value,authzid=\"".utf8_encode($this->auth)."\""; |
---|
686 | $response = base64_encode($reply); |
---|
687 | fputs($this->fp, "\"$response\"\r\n"); |
---|
688 | |
---|
689 | $this->line = fgets($this->fp, 1024); |
---|
690 | while(sieve::status($this->line) == F_DATA) |
---|
691 | $this->line = fgets($this->fp,1024); |
---|
692 | |
---|
693 | if(sieve::status($this->line) == F_NO) |
---|
694 | return false; |
---|
695 | $this->loggedin = TRUE; |
---|
696 | return TRUE; |
---|
697 | break; |
---|
698 | |
---|
699 | case "CRAM-MD5": |
---|
700 | // SASL CRAM-MD5 support works with timsieved 1.1.0 |
---|
701 | // follows rfc2195 for generating the $response to $challenge |
---|
702 | // CRAM-MD5 does not support proxy of $auth by $user |
---|
703 | // requires php mhash extension |
---|
704 | fputs($this->fp, "AUTHENTICATE \"CRAM-MD5\"\r\n"); |
---|
705 | // $clen is the length of the challenge line the server gives us |
---|
706 | $clen = fgets($this->fp, 1024); |
---|
707 | // read for 1024, should be long enough? |
---|
708 | $challenge = fgets($this->fp, 1024); |
---|
709 | // build a response to the challenge |
---|
710 | $hash = bin2hex(hmac_md5(base64_decode($challenge), $this->pass)); |
---|
711 | $response = base64_encode($this->user." ".$hash); |
---|
712 | // respond to the challenge string |
---|
713 | fputs($this->fp, "\"$response\"\r\n"); |
---|
714 | |
---|
715 | $this->line = fgets($this->fp, 1024); |
---|
716 | while(sieve::status($this->line) == F_DATA) |
---|
717 | $this->line = fgets($this->fp,1024); |
---|
718 | |
---|
719 | if(sieve::status($this->line) == F_NO) |
---|
720 | return false; |
---|
721 | $this->loggedin = TRUE; |
---|
722 | return TRUE; |
---|
723 | break; |
---|
724 | |
---|
725 | case "LOGIN": |
---|
726 | $login=base64_encode($this->user); |
---|
727 | $pass=base64_encode($this->pass); |
---|
728 | |
---|
729 | fputs($this->fp, "AUTHENTICATE \"LOGIN\"\r\n"); |
---|
730 | fputs($this->fp, "{".strlen($login)."+}\r\n"); |
---|
731 | fputs($this->fp, "$login\r\n"); |
---|
732 | fputs($this->fp, "{".strlen($pass)."+}\r\n"); |
---|
733 | fputs($this->fp, "$pass\r\n"); |
---|
734 | |
---|
735 | $this->line=fgets($this->fp,1024); |
---|
736 | while(sieve::status($this->line) == F_HEAD || |
---|
737 | sieve::status($this->line) == F_DATA) |
---|
738 | $this->line=fgets($this->fp,1024); |
---|
739 | |
---|
740 | if(sieve::status($this->line) == F_NO) |
---|
741 | return false; |
---|
742 | $this->loggedin=true; |
---|
743 | return true; |
---|
744 | break; |
---|
745 | |
---|
746 | default: |
---|
747 | echo 'default'; |
---|
748 | return false; |
---|
749 | break; |
---|
750 | |
---|
751 | }//end switch |
---|
752 | } |
---|
753 | |
---|
754 | /** |
---|
755 | * Return an array of available capabilities. |
---|
756 | * |
---|
757 | * @return array |
---|
758 | */ |
---|
759 | function sieve_get_capability() { |
---|
760 | if($this->loggedin==false) |
---|
761 | return false; |
---|
762 | fputs($this->fp, "CAPABILITY\r\n"); |
---|
763 | $this->line=fgets($this->fp,1024); |
---|
764 | |
---|
765 | //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard |
---|
766 | //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}" |
---|
767 | //So, if we see IMLEMENTATION in the first line, then we are done. |
---|
768 | |
---|
769 | if(ereg("IMPLEMENTATION",$this->line)) |
---|
770 | { |
---|
771 | //we're on the Cyrus V2 sieve server |
---|
772 | while(sieve::status($this->line) == F_DATA){ |
---|
773 | |
---|
774 | $this->item = sieve::parse_for_quotes($this->line); |
---|
775 | |
---|
776 | if(strcmp($this->item[0], "IMPLEMENTATION") == 0) |
---|
777 | $this->capabilities["implementation"] = $this->item[1]; |
---|
778 | |
---|
779 | elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){ |
---|
780 | |
---|
781 | if(strcmp($this->item[0], "SIEVE") == 0) |
---|
782 | $this->cap_type="modules"; |
---|
783 | else |
---|
784 | $this->cap_type="auth"; |
---|
785 | |
---|
786 | $this->modules = split(" ", $this->item[1]); |
---|
787 | if(is_array($this->modules)){ |
---|
788 | foreach($this->modules as $this->module) |
---|
789 | $this->capabilities[$this->cap_type][$this->module]=true; |
---|
790 | } /* end if */ |
---|
791 | elseif(is_string($this->modules)) |
---|
792 | $this->capabilites[$this->cap_type][$this->modules]=true; |
---|
793 | } |
---|
794 | else{ |
---|
795 | $this->capabilities["unknown"][]=$this->line; |
---|
796 | } |
---|
797 | $this->line=fgets($this->fp,1024); |
---|
798 | |
---|
799 | }// end while |
---|
800 | } |
---|
801 | else |
---|
802 | { |
---|
803 | //we're on the older Cyrus V1. server |
---|
804 | //this version does not support module reporting. We only have auth types. |
---|
805 | $this->cap_type="auth"; |
---|
806 | |
---|
807 | //break apart at the "Cyrus timsieve...." "SASL={......}" |
---|
808 | $this->item = sieve::parse_for_quotes($this->line); |
---|
809 | |
---|
810 | $this->capabilities["implementation"] = $this->item[0]; |
---|
811 | |
---|
812 | //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz} |
---|
813 | $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1); |
---|
814 | |
---|
815 | //then split again at the ", " stuff. |
---|
816 | $this->modules = split($this->modules, ", "); |
---|
817 | |
---|
818 | //fill up our $this->modules property |
---|
819 | if(is_array($this->modules)){ |
---|
820 | foreach($this->modules as $this->module) |
---|
821 | $this->capabilities[$this->cap_type][$this->module]=true; |
---|
822 | } /* end if */ |
---|
823 | elseif(is_string($this->modules)) |
---|
824 | $this->capabilites[$this->cap_type][$this->module]=true; |
---|
825 | } |
---|
826 | |
---|
827 | return $this->modules; |
---|
828 | } |
---|
829 | |
---|
830 | } |
---|
831 | |
---|
832 | |
---|
833 | /** |
---|
834 | * The following functions are support functions and might be handy to the |
---|
835 | * sieve class. |
---|
836 | */ |
---|
837 | |
---|
838 | if(!function_exists('hmac_md5')) { |
---|
839 | |
---|
840 | /** |
---|
841 | * Creates a HMAC digest that can be used for auth purposes. |
---|
842 | * See RFCs 2104, 2617, 2831 |
---|
843 | * Uses mhash() extension if available |
---|
844 | * |
---|
845 | * Squirrelmail has this function in functions/auth.php, and it might have been |
---|
846 | * included already. However, it helps remove the dependancy on mhash.so PHP |
---|
847 | * extension, for some sites. If mhash.so _is_ available, it is used for its |
---|
848 | * speed. |
---|
849 | * |
---|
850 | * This function is Copyright (c) 1999-2003 The SquirrelMail Project Team |
---|
851 | * Licensed under the GNU GPL. For full terms see the file COPYING. |
---|
852 | * |
---|
853 | * @param string $data Data to apply hash function to. |
---|
854 | * @param string $key Optional key, which, if supplied, will be used to |
---|
855 | * calculate data's HMAC. |
---|
856 | * @return string HMAC Digest string |
---|
857 | */ |
---|
858 | function hmac_md5($data, $key='') { |
---|
859 | // See RFCs 2104, 2617, 2831 |
---|
860 | // Uses mhash() extension if available |
---|
861 | if (extension_loaded('mhash')) { |
---|
862 | if ($key== '') { |
---|
863 | $mhash=mhash(MHASH_MD5,$data); |
---|
864 | } else { |
---|
865 | $mhash=mhash(MHASH_MD5,$data,$key); |
---|
866 | } |
---|
867 | return $mhash; |
---|
868 | } |
---|
869 | if (!$key) { |
---|
870 | return pack('H*',md5($data)); |
---|
871 | } |
---|
872 | $key = str_pad($key,64,chr(0x00)); |
---|
873 | if (strlen($key) > 64) { |
---|
874 | $key = pack("H*",md5($key)); |
---|
875 | } |
---|
876 | $k_ipad = $key ^ str_repeat(chr(0x36), 64) ; |
---|
877 | $k_opad = $key ^ str_repeat(chr(0x5c), 64) ; |
---|
878 | /* Heh, let's get recursive. */ |
---|
879 | $hmac=hmac_md5($k_opad . pack("H*",md5($k_ipad . $data)) ); |
---|
880 | return $hmac; |
---|
881 | } |
---|
882 | } |
---|
883 | |
---|
884 | /** |
---|
885 | * A hack to decode the challenge from timsieved 1.1.0. |
---|
886 | * |
---|
887 | * This function may not work with other versions and most certainly won't work |
---|
888 | * with other DIGEST-MD5 implentations |
---|
889 | * |
---|
890 | * @param $input string Challenge supplied by timsieved. |
---|
891 | */ |
---|
892 | function decode_challenge ($input) { |
---|
893 | $input = base64_decode($input); |
---|
894 | preg_match("/nonce=\"(.*)\"/U",$input, $matches); |
---|
895 | $resp['nonce'] = $matches[1]; |
---|
896 | preg_match("/realm=\"(.*)\"/U",$input, $matches); |
---|
897 | $resp['realm'] = $matches[1]; |
---|
898 | preg_match("/qop=\"(.*)\"/U",$input, $matches); |
---|
899 | $resp['qop'] = $matches[1]; |
---|
900 | return $resp; |
---|
901 | } |
---|
902 | |
---|
903 | // vim:ts=4:et:ft=php |
---|
904 | |
---|
905 | ?> |
---|