Code

Some changes.
[gosa.git] / include / sieve / 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                         $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] == "{"){
166         
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...
191            
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 */
199         
200         return true;
201         
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;
295  
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];
311         
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";
339        
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, ", ");
350  
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      }
381     
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");
413   
414     return sieve::get_response();
416   }  
417   
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   }
438   
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();
447    
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   }
461   
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) {
493     
494         case "PLAIN":
495             $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
496    
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()
525 ?>