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 $last_cnt_to_fetch = -1;
123 while($this->err_recv < ($this->err_len)){
125 /* We are trying to read the same count of chars
126 * as we tried last while loop.
127 * There must be something wrong-
128 */
129 if($last_cnt_to_fetch == ($this->err_len-$this->err_recv)){
130 $cur = $max;
131 echo "Sieve : failed to read error msgs, .. skipped endless loop! ~class_sieve.inc 525";
132 }
133 $last_cnt_to_fetch = $this->err_len-$this->err_recv;
134 $this->line = fgets($this->fp);//, ($this->err_len-$this->err_recv));
135 $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
136 $this->err_recv += strlen($this->line);
137 } /* end while */
139 $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug...
140 $this->error=EC_UNKNOWN;
142 } /* end if */
143 elseif($this->mtoken[0] == "("){
144 switch($this->mtoken){
145 case "(\"QUOTA\")":
146 $this->error = EC_QUOTA;
147 $this->error_raw=$this->rtoken;
148 break;
149 default:
150 $this->error = EC_UNKNOWN;
151 $this->error_raw=$this->rtoken;
152 break;
153 } /* end switch */
154 } /* end elseif */
155 else{
156 $this->error = EC_UNKNOWN;
157 $this->error_raw = $this->line;
158 }
159 return false;
161 } /* end if */
162 elseif(substr($this->token[0],0,-2) == "OK"){
163 return true;
164 } /* end elseif */
165 elseif($this->token[0][0] == "{"){
167 /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */
169 /* the first line is the len field {xx}, which we don't care about at this point */
170 $this->line = fgets($this->fp,1024);
171 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
172 $this->response[]=$this->line;
173 $this->line = fgets($this->fp, 1024);
174 }
175 if(substr($this->line,0,2) == "OK")
176 return true;
177 else
178 return false;
179 } /* end elseif */
180 elseif($this->token[0][0] == "\""){
182 /* I'm going under the _assumption_ that the only function that will get here is the listscripts().
183 I could very well be mistaken here, if I am, this part needs some rework */
185 $this->found_script=false;
187 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
188 $this->found_script=true;
189 list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
190 //hmmm, a bug in php, if there is no space on explode line, a warning is generated...
192 if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
193 $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1);
194 }
195 else
196 $this->response[] = substr(rtrim($this->ltoken),1,-1);
197 $this->line = fgets($this->fp, 1024);
198 } /* end while */
200 return true;
202 } /* end elseif */
203 else{
204 $this->error = EC_UNKNOWN;
205 $this->error_raw = $this->line;
206 print "<b><i>UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line</i></b><br>";
207 return false;
208 } /* end else */
209 } /* end get_response() */
211 function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5")
212 {
213 $this->host=$host;
214 $this->port=$port;
215 $this->user=$user;
216 $this->pass=$pass;
217 if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */
218 $this->auth = $this->user;
219 else
220 $this->auth = $auth;
221 $this->auth_types=$auth_types; /* Allowed authentication types */
222 $this->fp=0;
223 $this->line="";
224 $this->retval="";
225 $this->tmpfile="";
226 $this->fh=0;
227 $this->len=0;
228 $this->capabilities="";
229 $this->loggedin=false;
230 $this->error= "";
231 $this->error_raw="";
232 }
234 function parse_for_quotes($string)
235 {
236 /* This function tokenizes a line of input by quote marks and returns them as an array */
238 $start = -1;
239 $index = 0;
241 for($ptr = 0; $ptr < strlen($string); $ptr++){
242 if($string[$ptr] == '"' and $string[$ptr] != '\\'){
243 if($start == -1){
244 $start = $ptr;
245 } /* end if */
246 else{
247 $token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
248 $found = true;
249 $start = -1;
250 } /* end else */
252 } /* end if */
254 } /* end for */
256 if(isset($token))
257 return $token;
258 else
259 return false;
260 } /* end function */
262 function status($string)
263 {
264 //this should probably be replaced by a smarter parser.
266 /* Need to remove this and all dependencies from the class */
268 switch (substr($string, 0,2)){
269 case "NO":
270 return F_NO; //there should be some function to extract the error code from this line
271 //NO ("quota") "You are oly allowed x number of scripts"
272 break;
273 case "OK":
274 return F_OK;
275 break;
276 default:
277 switch ($string[0]){
278 case "{":
279 //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter?
280 return F_HEAD;
281 break;
282 default:
283 return F_DATA;
284 break;
285 } /* end switch */
286 } /* end switch */
287 } /* end status() */
289 function sieve_login()
290 {
292 $this->fp=fsockopen($this->host,$this->port);
293 if($this->fp == false)
294 return false;
296 $this->line=fgets($this->fp,1024);
298 //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard
299 //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
300 //So, if we see IMLEMENTATION in the first line, then we are done.
302 if(ereg("IMPLEMENTATION",$this->line))
303 {
304 //we're on the Cyrus V2 sieve server
305 while(sieve::status($this->line) == F_DATA){
307 $this->item = sieve::parse_for_quotes($this->line);
309 if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
310 $this->capabilities["implementation"] = $this->item[1];
312 elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
314 if(strcmp($this->item[0], "SIEVE") == 0)
315 $this->cap_type="modules";
316 else
317 $this->cap_type="auth";
319 $this->modules = split(" ", $this->item[1]);
320 if(is_array($this->modules)){
321 foreach($this->modules as $this->module)
322 $this->capabilities[$this->cap_type][$this->module]=true;
323 } /* end if */
324 elseif(is_string($this->modules))
325 $this->capabilites[$this->cap_type][$this->modules]=true;
326 }
327 else{
328 $this->capabilities["unknown"][]=$this->line;
329 }
330 $this->line=fgets($this->fp,1024);
332 }// end while
333 }
334 else
335 {
336 //we're on the older Cyrus V1. server
337 //this version does not support module reporting. We only have auth types.
338 $this->cap_type="auth";
340 //break apart at the "Cyrus timsieve...." "SASL={......}"
341 $this->item = sieve::parse_for_quotes($this->line);
343 $this->capabilities["implementation"] = $this->item[0];
345 //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz}
346 $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
348 //then split again at the ", " stuff.
349 $this->modules = split($this->modules, ", ");
351 //fill up our $this->modules property
352 if(is_array($this->modules)){
353 foreach($this->modules as $this->module)
354 $this->capabilities[$this->cap_type][$this->module]=true;
355 } /* end if */
356 elseif(is_string($this->modules))
357 $this->capabilites[$this->cap_type][$this->module]=true;
358 }
363 if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes?
364 $this->error=EC_UNKNOWN;
365 $this->error_raw = "Server not allowing connections.";
366 return false;
367 }
369 /* decision login to decide what type of authentication to use... */
371 /* Loop through each allowed authentication type and see if the server allows the type */
372 foreach(split(" ",$this->auth_types) as $auth_type)
373 {
374 if ($this->capabilities["auth"][$auth_type])
375 {
376 /* We found an auth type that is allowed. */
377 $this->auth_in_use = $auth_type;
378 break;
379 }
380 }
382 /* Fill error message if no auth types are present */
383 if (!isset($this->capabilities["auth"])){
384 $this->error=EC_UNKNOWN;
385 $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules";
386 return false;
387 }
389 /* call our authentication program */
390 return sieve::authenticate();
392 }
394 function sieve_logout()
395 {
396 if($this->loggedin==false)
397 return false;
399 fputs($this->fp,"LOGOUT\r\n");
400 fclose($this->fp);
401 $this->loggedin=false;
402 return true;
403 }
405 function sieve_sendscript($scriptname, $script)
406 {
407 if($this->loggedin==false)
408 return false;
409 $this->script=stripslashes($script);
410 $len=strlen($this->script);
411 fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n");
412 fputs($this->fp, "$this->script\r\n");
414 return sieve::get_response();
416 }
418 //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src.
419 //don't expect this function to work yet. I might have messed something up here, too.
420 function sieve_havespace($scriptname, $scriptsize)
421 {
422 if($this->loggedin==false)
423 return false;
424 fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
425 return sieve::get_response();
427 }
429 function sieve_setactivescript($scriptname)
430 {
431 if($this->loggedin==false)
432 return false;
434 fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");
435 return sieve::get_response();
437 }
439 function sieve_getscript($scriptname)
440 {
441 unset($this->script);
442 if($this->loggedin==false)
443 return false;
445 fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
446 return sieve::get_response();
448 }
451 function sieve_deletescript($scriptname)
452 {
453 if($this->loggedin==false)
454 return false;
456 fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");
458 return sieve::get_response();
459 }
462 function sieve_listscripts()
463 {
464 fputs($this->fp, "LISTSCRIPTS\r\n");
465 sieve::get_response(); //should always return true, even if there are no scripts...
466 if(isset($this->found_script) and $this->found_script)
467 return true;
468 else{
469 $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found...
470 $this->error_raw="No scripts found for this account.";
471 return false;
472 }
473 }
475 function sieve_alive()
476 {
477 if(!isset($this->fp) or $this->fp==0){
478 $this->error = EC_NOT_LOGGED_IN;
479 return false;
480 }
481 elseif(feof($this->fp)){
482 $this->error = EC_NOT_LOGGED_IN;
483 return false;
484 }
485 else
486 return true;
487 }
489 function authenticate()
490 {
492 switch ($this->auth_in_use) {
494 case "PLAIN":
495 $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
497 $this->len=strlen($auth);
498 fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n");
499 fputs($this->fp, "$auth\r\n");
501 $this->line=fgets($this->fp,1024);
502 while(sieve::status($this->line) == F_DATA)
503 $this->line=fgets($this->fp,1024);
505 if(sieve::status($this->line) == F_NO)
506 return false;
507 $this->loggedin=true;
508 return true;
509 break;
511 default:
512 return false;
513 break;
515 }//end switch
518 }//end authenticate()
521 }
525 ?>