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