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 /* Ensure max loop of 1000, to keep the ui from freezing */
123 $max = 1000;
124 $cur = 0;
126 while($this->err_recv < ($this->err_len) && ($cur < $max)){
128 $cur ++;
129 $this->line = fgets($this->fp,4096);
130 $this->err_recv += strlen($this->line);
131 $this->error_raw[]=preg_replace("/\r\n/","",$this->line); //we want to be nice and strip crlf'
132 }
133 } /* end if */
134 elseif($this->mtoken[0] == "("){
135 switch($this->mtoken){
136 case "(\"QUOTA\")":
137 $this->error = EC_QUOTA;
138 $this->error_raw=$this->rtoken;
139 break;
140 default:
141 $this->error = EC_UNKNOWN;
142 $this->error_raw=$this->rtoken;
143 break;
144 } /* end switch */
145 } /* end elseif */
146 else{
147 $this->error = EC_UNKNOWN;
148 $this->error_raw = $this->line;
149 }
150 return false;
152 } /* end if */
153 elseif(substr($this->token[0],0,-2) == "OK"){
154 return true;
155 } /* end elseif */
156 elseif($this->token[0][0] == "{"){
158 /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */
160 /* the first line is the len field {xx}, which we don't care about at this point */
161 $this->line = fgets($this->fp,1024);
162 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
163 $this->response[]=$this->line;
164 $this->line = fgets($this->fp, 1024);
165 }
166 if(substr($this->line,0,2) == "OK")
167 return true;
168 else
169 return false;
170 } /* end elseif */
171 elseif($this->token[0][0] == "\""){
173 /* I'm going under the _assumption_ that the only function that will get here is the listscripts().
174 I could very well be mistaken here, if I am, this part needs some rework */
176 $this->found_script=false;
178 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
179 $this->found_script=true;
180 list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
181 //hmmm, a bug in php, if there is no space on explode line, a warning is generated...
183 if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
184 $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1);
185 }
186 else
187 $this->response[] = substr(rtrim($this->ltoken),1,-1);
188 $this->line = fgets($this->fp, 1024);
189 } /* end while */
191 return true;
193 } /* end elseif */
194 else{
195 $this->error = EC_UNKNOWN;
196 $this->error_raw = $this->line;
197 print "<b><i>UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line</i></b><br>";
198 return false;
199 } /* end else */
200 } /* end get_response() */
202 function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5")
203 {
204 $this->host=$host;
205 $this->port=$port;
206 $this->user=$user;
207 $this->pass=$pass;
208 if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */
209 $this->auth = $this->user;
210 else
211 $this->auth = $auth;
212 $this->auth_types=$auth_types; /* Allowed authentication types */
213 $this->fp=0;
214 $this->line="";
215 $this->retval="";
216 $this->tmpfile="";
217 $this->fh=0;
218 $this->len=0;
219 $this->capabilities="";
220 $this->loggedin=false;
221 $this->error= "";
222 $this->error_raw="";
223 }
225 function parse_for_quotes($string)
226 {
227 /* This function tokenizes a line of input by quote marks and returns them as an array */
229 $start = -1;
230 $index = 0;
232 for($ptr = 0; $ptr < strlen($string); $ptr++){
233 if($string[$ptr] == '"' and $string[$ptr] != '\\'){
234 if($start == -1){
235 $start = $ptr;
236 } /* end if */
237 else{
238 $token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
239 $found = true;
240 $start = -1;
241 } /* end else */
243 } /* end if */
245 } /* end for */
247 if(isset($token))
248 return $token;
249 else
250 return false;
251 } /* end function */
253 function status($string)
254 {
255 //this should probably be replaced by a smarter parser.
257 /* Need to remove this and all dependencies from the class */
259 switch (substr($string, 0,2)){
260 case "NO":
261 return F_NO; //there should be some function to extract the error code from this line
262 //NO ("quota") "You are oly allowed x number of scripts"
263 break;
264 case "OK":
265 return F_OK;
266 break;
267 default:
268 switch ($string[0]){
269 case "{":
270 //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter?
271 return F_HEAD;
272 break;
273 default:
274 return F_DATA;
275 break;
276 } /* end switch */
277 } /* end switch */
278 } /* end status() */
280 function sieve_login()
281 {
283 $this->fp=@fsockopen($this->host,$this->port);
284 if($this->fp == false)
285 return false;
287 $this->line=fgets($this->fp,1024);
289 //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard
290 //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
291 //So, if we see IMLEMENTATION in the first line, then we are done.
293 if(ereg("IMPLEMENTATION",$this->line))
294 {
295 //we're on the Cyrus V2 sieve server
296 while(sieve::status($this->line) == F_DATA){
298 $this->item = sieve::parse_for_quotes($this->line);
300 if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
301 $this->capabilities["implementation"] = $this->item[1];
303 elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
305 if(strcmp($this->item[0], "SIEVE") == 0)
306 $this->cap_type="modules";
307 else
308 $this->cap_type="auth";
310 $this->modules = split(" ", $this->item[1]);
311 if(is_array($this->modules)){
312 foreach($this->modules as $this->module)
313 $this->capabilities[$this->cap_type][$this->module]=true;
314 } /* end if */
315 elseif(is_string($this->modules))
316 $this->capabilites[$this->cap_type][$this->modules]=true;
317 }
318 else{
319 $this->capabilities["unknown"][]=$this->line;
320 }
321 $this->line=fgets($this->fp,1024);
323 }// end while
324 }
325 else
326 {
327 //we're on the older Cyrus V1. server
328 //this version does not support module reporting. We only have auth types.
329 $this->cap_type="auth";
331 //break apart at the "Cyrus timsieve...." "SASL={......}"
332 $this->item = sieve::parse_for_quotes($this->line);
334 $this->capabilities["implementation"] = $this->item[0];
336 //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz}
337 $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
339 //then split again at the ", " stuff.
340 $this->modules = split($this->modules, ", ");
342 //fill up our $this->modules property
343 if(is_array($this->modules)){
344 foreach($this->modules as $this->module)
345 $this->capabilities[$this->cap_type][$this->module]=true;
346 } /* end if */
347 elseif(is_string($this->modules))
348 $this->capabilites[$this->cap_type][$this->module]=true;
349 }
354 if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes?
355 $this->error=EC_UNKNOWN;
356 $this->error_raw = "Server not allowing connections.";
357 return false;
358 }
360 /* decision login to decide what type of authentication to use... */
362 /* Loop through each allowed authentication type and see if the server allows the type */
363 foreach(split(" ",$this->auth_types) as $auth_type)
364 {
365 if ($this->capabilities["auth"][$auth_type])
366 {
367 /* We found an auth type that is allowed. */
368 $this->auth_in_use = $auth_type;
369 break;
370 }
371 }
373 /* Fill error message if no auth types are present */
374 if (!isset($this->capabilities["auth"])){
375 $this->error=EC_UNKNOWN;
376 $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules";
377 return false;
378 }
380 /* call our authentication program */
381 return sieve::authenticate();
383 }
385 function sieve_logout()
386 {
387 if($this->loggedin==false)
388 return false;
390 fputs($this->fp,"LOGOUT\r\n");
391 fclose($this->fp);
392 $this->loggedin=false;
393 return true;
394 }
396 function sieve_sendscript($scriptname, $script)
397 {
398 if($this->loggedin==false)
399 return false;
400 $this->script=stripslashes($script);
401 $len=strlen($this->script);
402 fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n");
403 fputs($this->fp, "$this->script\r\n");
405 return sieve::get_response();
407 }
409 //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src.
410 //don't expect this function to work yet. I might have messed something up here, too.
411 function sieve_havespace($scriptname, $scriptsize)
412 {
413 if($this->loggedin==false)
414 return false;
415 fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
416 return sieve::get_response();
418 }
420 function sieve_setactivescript($scriptname)
421 {
422 if($this->loggedin==false)
423 return false;
425 fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");
426 return sieve::get_response();
428 }
430 function sieve_getscript($scriptname)
431 {
432 unset($this->script);
433 if($this->loggedin==false)
434 return false;
436 fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
437 return sieve::get_response();
439 }
442 function sieve_deletescript($scriptname)
443 {
444 if($this->loggedin==false)
445 return false;
447 fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");
449 return sieve::get_response();
450 }
453 function sieve_listscripts()
454 {
455 fputs($this->fp, "LISTSCRIPTS\r\n");
456 sieve::get_response(); //should always return true, even if there are no scripts...
457 if(isset($this->found_script) and $this->found_script)
458 return true;
459 else{
460 $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found...
461 $this->error_raw="No scripts found for this account.";
462 return false;
463 }
464 }
466 function sieve_alive()
467 {
468 if(!isset($this->fp) or $this->fp==0){
469 $this->error = EC_NOT_LOGGED_IN;
470 return false;
471 }
472 elseif(feof($this->fp)){
473 $this->error = EC_NOT_LOGGED_IN;
474 return false;
475 }
476 else
477 return true;
478 }
480 function authenticate()
481 {
483 switch ($this->auth_in_use) {
485 case "PLAIN":
486 $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
488 $this->len=strlen($auth);
489 fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n");
490 fputs($this->fp, "$auth\r\n");
492 $this->line=fgets($this->fp,1024);
493 while(sieve::status($this->line) == F_DATA)
494 $this->line=fgets($this->fp,1024);
496 if(sieve::status($this->line) == F_NO)
497 return false;
498 $this->loggedin=true;
499 return true;
500 break;
502 default:
503 return false;
504 break;
506 }//end switch
509 }//end authenticate()
512 }
516 ?>