1 <?
3 /*
4 * $Id: class_sieve.inc,v 1.1 2005/02/21 09:33:01 cajus Exp $
5 *
6 * Copyright 2001 Dan Ellis <danellis@rushmore.com>
7 *
8 * See the enclosed file COPYING for license information (GPL). If you
9 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
10 */
12 // TODO before next release: remove ::status() and dependencies
15 define ("F_NO", 0);
16 define ("F_OK", 1);
17 define ("F_DATA", 2);
18 define ("F_HEAD", 3);
20 define ("EC_NOT_LOGGED_IN", 0);
21 define ("EC_QUOTA", 10);
22 define ("EC_NOSCRIPTS", 20);
23 define ("EC_UNKNOWN", 255);
24 /*
26 SIEVE-PHP.LIB VERSION 0.0.8
28 (C) 2001 Dan Ellis.
30 PLEASE READ THE README FILE FOR MORE INFORMATION.
32 Basically, this is the first re-release. Things are much better than before.
34 Notes:
35 This program/libary has bugs.
36 . This was quickly hacked out, so please let me know what is wrong and if you feel ambitious submit
37 a patch :).
39 Todo:
40 . Provide better error diagnostics. (mostly done with ver 0.0.5)
41 . Allow other auth mechanisms besides plain (in progress)
42 . Have timing mechanism when port problems arise. (not done yet)
43 . Maybe add the NOOP function. (not done yet)
44 . Other top secret stuff.... (some done, believe me?)
46 Dan Ellis (danellis@rushmore.com)
48 This program is released under the GNU Public License.
50 You should have received a copy of the GNU Public
51 License along with this package; if not, write to the
52 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
53 Boston, MA 02111-1307, USA.
55 See CHANGES for updates since last release
57 Contributers of patches:
58 Atif Ghaffar
59 Andrew Sterling Hanenkamp <sterling@hanenkamp.com>
60 */
63 class sieve
64 {
65 var $host;
66 var $port;
67 var $user;
68 var $pass;
69 var $auth_types; /* a comma seperated list of allowed auth types, in order of preference */
70 var $auth_in_use; /* type of authentication attempted */
72 var $line;
73 var $fp;
74 var $retval;
75 var $tmpfile;
76 var $fh;
77 var $len;
78 var $script;
80 var $loggedin;
81 var $capabilities;
82 var $error;
83 var $error_raw;
84 var $responses;
86 //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN)
87 //so we can decide how to handle certain errors?!?
89 //also add a connection type, like PLAIN, MD5, etc...
92 function get_response()
93 {
94 if($this->loggedin == false or feof($this->fp)){
95 $this->error = EC_NOT_LOGGED_IN;
96 $this->error_raw = "You are not logged in.";
97 return false;
98 }
100 unset($this->response);
101 unset($this->error);
102 unset($this->error_raw);
104 $this->line=fgets($this->fp,1024);
105 $this->token = split(" ", $this->line, 2);
107 if($this->token[0] == "NO"){
108 /* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of:
109 NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */
110 $this->x = 0;
111 list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3);
112 if($this->mtoken[0] == "{"){
113 while($this->mtoken[$this->x] != "}" or $this->err_len < 1){
114 $this->err_len = substr($this->mtoken, 1, $this->x);
115 $this->x++;
116 }
117 //print "<br>Trying to receive $this->err_len bytes for result<br>";
118 $this->line = fgets($this->fp,$this->err_len);
119 $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
120 $this->err_recv = strlen($this->line);
122 /* Avoid loop till memory is full
123 This can happen if sieve script is modified via external scripts and not by gosa .. */
124 $max = 10000;
125 $cur = 0 ;
126 while($this->err_recv < $this->err_len && ($cur < $max)){
127 //print "<br>Trying to receive ".($this->err_len-$this->err_recv)." bytes for result<br>";
128 $cur ++ ;
129 $this->line = fgets($this->fp, ($this->err_len-$this->err_recv));
130 $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
131 $this->err_recv += strlen($this->line);
132 } /* end while */
133 $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug...
134 $this->error=EC_UNKNOWN;
136 } /* end if */
137 elseif($this->mtoken[0] == "("){
138 switch($this->mtoken){
139 case "(\"QUOTA\")":
140 $this->error = EC_QUOTA;
141 $this->error_raw=$this->rtoken;
142 break;
143 default:
144 $this->error = EC_UNKNOWN;
145 $this->error_raw=$this->rtoken;
146 break;
147 } /* end switch */
148 } /* end elseif */
149 else{
150 $this->error = EC_UNKNOWN;
151 $this->error_raw = $this->line;
152 }
153 return false;
155 } /* end if */
156 elseif(substr($this->token[0],0,-2) == "OK"){
157 return true;
158 } /* end elseif */
159 elseif($this->token[0][0] == "{"){
161 /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */
163 /* the first line is the len field {xx}, which we don't care about at this point */
164 $this->line = fgets($this->fp,1024);
165 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
166 $this->response[]=$this->line;
167 $this->line = fgets($this->fp, 1024);
168 }
169 if(substr($this->line,0,2) == "OK")
170 return true;
171 else
172 return false;
173 } /* end elseif */
174 elseif($this->token[0][0] == "\""){
176 /* I'm going under the _assumption_ that the only function that will get here is the listscripts().
177 I could very well be mistaken here, if I am, this part needs some rework */
179 $this->found_script=false;
181 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
182 $this->found_script=true;
183 list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
184 //hmmm, a bug in php, if there is no space on explode line, a warning is generated...
186 if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
187 $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1);
188 }
189 else
190 $this->response[] = substr(rtrim($this->ltoken),1,-1);
191 $this->line = fgets($this->fp, 1024);
192 } /* end while */
194 return true;
196 } /* end elseif */
197 else{
198 $this->error = EC_UNKNOWN;
199 $this->error_raw = $this->line;
200 print "<b><i>UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line</i></b><br>";
201 return false;
202 } /* end else */
203 } /* end get_response() */
205 function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5")
206 {
207 $this->host=$host;
208 $this->port=$port;
209 $this->user=$user;
210 $this->pass=$pass;
211 if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */
212 $this->auth = $this->user;
213 else
214 $this->auth = $auth;
215 $this->auth_types=$auth_types; /* Allowed authentication types */
216 $this->fp=0;
217 $this->line="";
218 $this->retval="";
219 $this->tmpfile="";
220 $this->fh=0;
221 $this->len=0;
222 $this->capabilities="";
223 $this->loggedin=false;
224 $this->error= "";
225 $this->error_raw="";
226 }
228 function parse_for_quotes($string)
229 {
230 /* This function tokenizes a line of input by quote marks and returns them as an array */
232 $start = -1;
233 $index = 0;
235 for($ptr = 0; $ptr < strlen($string); $ptr++){
236 if($string[$ptr] == '"' and $string[$ptr] != '\\'){
237 if($start == -1){
238 $start = $ptr;
239 } /* end if */
240 else{
241 $token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
242 $found = true;
243 $start = -1;
244 } /* end else */
246 } /* end if */
248 } /* end for */
250 if(isset($token))
251 return $token;
252 else
253 return false;
254 } /* end function */
256 function status($string)
257 {
258 //this should probably be replaced by a smarter parser.
260 /* Need to remove this and all dependencies from the class */
262 switch (substr($string, 0,2)){
263 case "NO":
264 return F_NO; //there should be some function to extract the error code from this line
265 //NO ("quota") "You are oly allowed x number of scripts"
266 break;
267 case "OK":
268 return F_OK;
269 break;
270 default:
271 switch ($string[0]){
272 case "{":
273 //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter?
274 return F_HEAD;
275 break;
276 default:
277 return F_DATA;
278 break;
279 } /* end switch */
280 } /* end switch */
281 } /* end status() */
283 function sieve_login()
284 {
286 $this->fp=fsockopen($this->host,$this->port);
287 if($this->fp == false)
288 return false;
290 $this->line=fgets($this->fp,1024);
292 //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard
293 //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
294 //So, if we see IMLEMENTATION in the first line, then we are done.
296 if(ereg("IMPLEMENTATION",$this->line))
297 {
298 //we're on the Cyrus V2 sieve server
299 while(sieve::status($this->line) == F_DATA){
301 $this->item = sieve::parse_for_quotes($this->line);
303 if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
304 $this->capabilities["implementation"] = $this->item[1];
306 elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
308 if(strcmp($this->item[0], "SIEVE") == 0)
309 $this->cap_type="modules";
310 else
311 $this->cap_type="auth";
313 $this->modules = split(" ", $this->item[1]);
314 if(is_array($this->modules)){
315 foreach($this->modules as $this->module)
316 $this->capabilities[$this->cap_type][$this->module]=true;
317 } /* end if */
318 elseif(is_string($this->modules))
319 $this->capabilites[$this->cap_type][$this->modules]=true;
320 }
321 else{
322 $this->capabilities["unknown"][]=$this->line;
323 }
324 $this->line=fgets($this->fp,1024);
326 }// end while
327 }
328 else
329 {
330 //we're on the older Cyrus V1. server
331 //this version does not support module reporting. We only have auth types.
332 $this->cap_type="auth";
334 //break apart at the "Cyrus timsieve...." "SASL={......}"
335 $this->item = sieve::parse_for_quotes($this->line);
337 $this->capabilities["implementation"] = $this->item[0];
339 //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz}
340 $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
342 //then split again at the ", " stuff.
343 $this->modules = split($this->modules, ", ");
345 //fill up our $this->modules property
346 if(is_array($this->modules)){
347 foreach($this->modules as $this->module)
348 $this->capabilities[$this->cap_type][$this->module]=true;
349 } /* end if */
350 elseif(is_string($this->modules))
351 $this->capabilites[$this->cap_type][$this->module]=true;
352 }
357 if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes?
358 $this->error=EC_UNKNOWN;
359 $this->error_raw = "Server not allowing connections.";
360 return false;
361 }
363 /* decision login to decide what type of authentication to use... */
365 /* Loop through each allowed authentication type and see if the server allows the type */
366 foreach(split(" ",$this->auth_types) as $auth_type)
367 {
368 if ($this->capabilities["auth"][$auth_type])
369 {
370 /* We found an auth type that is allowed. */
371 $this->auth_in_use = $auth_type;
372 break;
373 }
374 }
376 /* Fill error message if no auth types are present */
377 if (!isset($this->capabilities["auth"])){
378 $this->error=EC_UNKNOWN;
379 $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules";
380 return false;
381 }
383 /* call our authentication program */
384 return sieve::authenticate();
386 }
388 function sieve_logout()
389 {
390 if($this->loggedin==false)
391 return false;
393 fputs($this->fp,"LOGOUT\r\n");
394 fclose($this->fp);
395 $this->loggedin=false;
396 return true;
397 }
399 function sieve_sendscript($scriptname, $script)
400 {
401 if($this->loggedin==false)
402 return false;
403 $this->script=stripslashes($script);
404 $len=strlen($this->script);
405 fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n");
406 fputs($this->fp, "$this->script\r\n");
408 return sieve::get_response();
410 }
412 //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src.
413 //don't expect this function to work yet. I might have messed something up here, too.
414 function sieve_havespace($scriptname, $scriptsize)
415 {
416 if($this->loggedin==false)
417 return false;
418 fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
419 return sieve::get_response();
421 }
423 function sieve_setactivescript($scriptname)
424 {
425 if($this->loggedin==false)
426 return false;
428 fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");
429 return sieve::get_response();
431 }
433 function sieve_getscript($scriptname)
434 {
435 unset($this->script);
436 if($this->loggedin==false)
437 return false;
439 fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
440 return sieve::get_response();
442 }
445 function sieve_deletescript($scriptname)
446 {
447 if($this->loggedin==false)
448 return false;
450 fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");
452 return sieve::get_response();
453 }
456 function sieve_listscripts()
457 {
458 fputs($this->fp, "LISTSCRIPTS\r\n");
459 sieve::get_response(); //should always return true, even if there are no scripts...
460 if(isset($this->found_script) and $this->found_script)
461 return true;
462 else{
463 $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found...
464 $this->error_raw="No scripts found for this account.";
465 return false;
466 }
467 }
469 function sieve_alive()
470 {
471 if(!isset($this->fp) or $this->fp==0){
472 $this->error = EC_NOT_LOGGED_IN;
473 return false;
474 }
475 elseif(feof($this->fp)){
476 $this->error = EC_NOT_LOGGED_IN;
477 return false;
478 }
479 else
480 return true;
481 }
483 function authenticate()
484 {
486 switch ($this->auth_in_use) {
488 case "PLAIN":
489 $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
491 $this->len=strlen($auth);
492 fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n");
493 fputs($this->fp, "$auth\r\n");
495 $this->line=fgets($this->fp,1024);
496 while(sieve::status($this->line) == F_DATA)
497 $this->line=fgets($this->fp,1024);
499 if(sieve::status($this->line) == F_NO)
500 return false;
501 $this->loggedin=true;
502 return true;
503 break;
505 default:
506 return false;
507 break;
509 }//end switch
512 }//end authenticate()
515 }
519 ?>