Code

Udpated todo
[gosa.git] / include / class_sieve.inc
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 */
71   
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;
130       
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] == "{"){
155         
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...
180            
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 */
188         
189         return true;
190         
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;
284  
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];
300         
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";
328        
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, ", ");
339  
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      }
370     
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");
402   
403     return sieve::get_response();
405   }  
406   
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   }
427   
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();
436    
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   }
450   
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) {
482     
483         case "PLAIN":
484             $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
485    
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()
514 ?>