Code

fixes: #1175
[gosa.git] / gosa-plugins / mail / personal / mail / sieve / class_sieve.inc
1 <?php
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;
85   var $options = "";
87   //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN)
88   //so we can decide how to handle certain errors?!?
90   //also add a connection type, like PLAIN, MD5, etc...
93   function get_response()
94   {
95     if($this->loggedin == false or feof($this->fp)){
96       $this->error = EC_NOT_LOGGED_IN;
97       $this->error_raw = "You are not logged in.";
98       return false;
99     }
101     unset($this->response);
102     unset($this->error);
103     unset($this->error_raw);
105     $this->line=fgets($this->fp,1024);
106     $this->token = explode(" ", $this->line, 2);
108     if($this->token[0] == "NO"){
109       /* we need to try and extract the error code from here.  There are two possibilites: one, that it will take the form of:
110          NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */
111       $this->x = 0;
112       list($this->ltoken, $this->mtoken, $this->rtoken) = explode(" ", $this->line." ", 3);
113       if($this->mtoken[0] == "{"){
114         while($this->mtoken[$this->x] != "}" or $this->err_len < 1){
115           $this->err_len = substr($this->mtoken, 1, $this->x);
116           $this->x++;    
117         }
118 #print "<br>Trying to receive $this->err_len bytes for result<br>";
119         $this->line = fgets($this->fp,$this->err_len);
120         $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2);    //we want to be nice and strip crlf's
121         $this->err_recv = strlen($this->line);
123         /* Ensure max loop of 1000, to keep the ui from freezing */
124         $max = 1000;
125         $cur = 0;
127         while($this->err_recv < ($this->err_len) && ($cur < $max)){
129           $cur ++;
130           $this->line    = fgets($this->fp,4096);
131           $this->err_recv += strlen($this->line);
132           $this->error_raw[]=preg_replace("/\r\n/","",$this->line);    //we want to be nice and strip crlf'
133         } 
134       } /* end if */
135       elseif($this->mtoken[0] == "("){
136         switch($this->mtoken){
137           case "(\"QUOTA\")":
138             $this->error = EC_QUOTA;
139           $this->error_raw=$this->rtoken;
140           break;
141           default:
142           $this->error = EC_UNKNOWN;
143           $this->error_raw=$this->rtoken;
144           break;
145         } /* end switch */
146       } /* end elseif */
147       else{
148         $this->error = EC_UNKNOWN;
149         $this->error_raw = $this->line;
150       }     
151       return false;
153     } /* end if */
154     elseif(substr($this->token[0],0,-2) == "OK"){
155       return true;
156     } /* end elseif */
157     elseif($this->token[0][0] == "{"){
159       /* Unable wild assumption:  that the only function that gets here is the get_script(), doesn't really matter though */       
161       /* the first line is the len field {xx}, which we don't care about at this point */
162       $this->line = fgets($this->fp,1024);
163       while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
164         $this->response[]=$this->line;
165         $this->line = fgets($this->fp, 1024);
166       }
167       if(substr($this->line,0,2) == "OK")
168         return true;
169       else
170         return false;
171     } /* end elseif */
172     elseif($this->token[0][0] == "\""){
174       /* I'm going under the _assumption_ that the only function that will get here is the listscripts().
175          I could very well be mistaken here, if I am, this part needs some rework */
177       $this->found_script=false;        
179       while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
180         $this->found_script=true;
181         list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
182         //hmmm, a bug in php, if there is no space on explode line, a warning is generated...
184         if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
185           $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1);  
186         }
187         else
188           $this->response[] = substr(rtrim($this->ltoken),1,-1);
189         $this->line = fgets($this->fp, 1024);
190       } /* end while */
192       return true;
194     } /* end elseif */
195     else{
196       $this->error = EC_UNKNOWN;
197       $this->error_raw = $this->line;
198       print "<b><i>UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line</i></b><br>";
199       return false;
200     } /* end else */   
201   } /* end get_response() */
203   function sieve($host, $port, $user, $pass, $auth="",$options ="", $auth_types="PLAIN DIGEST-MD5")
204   {
205     $this->host=$host;
206     $this->port=$port;
207     $this->user=$user;
208     $this->pass=$pass;
209     if(!strcmp($auth, ""))              /* If there is no auth user, we deem the user itself to be the auth'd user */
210       $this->auth = $this->user;
211     else
212       $this->auth = $auth;
213     $this->auth_types=$auth_types;      /* Allowed authentication types */
214     $this->fp=0;
215     $this->line="";
216     $this->retval="";
217     $this->tmpfile="";
218     $this->fh=0;
219     $this->len=0;
220     $this->capabilities="";
221     $this->loggedin=false;
222     $this->error= "";
223     $this->error_raw="";
224     $this->options = $options;
226     @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__, $this->error_raw,
227         "<b>SIEVE: Host: $host:$port. Options: $options - Auth Types: $auth_types </b>");
228   }
230   function parse_for_quotes($string)
231   {
232     /* This function tokenizes a line of input by quote marks and returns them as an array */
234     $start = -1;
235     $index = 0;
237     for($ptr = 0; $ptr < strlen($string); $ptr++){
238       if($string[$ptr] == '"' and $string[$ptr] != '\\'){
239         if($start == -1){
240           $start = $ptr;
241         } /* end if */
242         else{
243           $token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
244           $found = true;
245           $start = -1;
246         } /* end else */
248       } /* end if */  
250     } /* end for */
252     if(isset($token))
253       return $token;
254     else
255       return false;
256   } /* end function */            
258   function status($string)
259   {
260     //this should probably be replaced by a smarter parser.
262     /*  Need to remove this and all dependencies from the class */
264     switch (substr($string, 0,2)){
265       case "NO":
266         return F_NO;            //there should be some function to extract the error code from this line
267       //NO ("quota") "You are oly allowed x number of scripts"
268       break;
269       case "OK":
270         return F_OK;
271       break;
272       default:
273       switch ($string[0]){
274         case "{":
275           //do parse here for {}'s  maybe modify parse_for_quotes to handle any parse delimiter?
276           return F_HEAD;
277         break;
278         default:
279         return F_DATA;
280         break;
281       } /* end switch */
282     } /* end switch */
283   } /* end status() */
285   function sieve_login()
286   {
288     $this->fp=@fsockopen($this->host,$this->port,$err_id,$err_str);
289     if($this->fp == false){
291       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,$this->error_raw,
292           "<b>SIEVE: Socket connection failed. (".$this->host.":".$this->port.")</b>");
294       $this->error_raw = $err_str;
295       return false;
296     }
298     $this->line=fgets($this->fp,1024);
300     //Hack for older versions of Sieve Server.  They do not respond with the Cyrus v2. standard
301     //response.  They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
302     //So, if we see IMLEMENTATION in the first line, then we are done.
304     if(strstr($this->line, "IMPLEMENTATION"))
305     {
306       //we're on the Cyrus V2 sieve server
307       while(sieve::status($this->line) == F_DATA){
309         $this->item = sieve::parse_for_quotes($this->line);
311         if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
312           $this->capabilities["implementation"] = $this->item[1];
314         elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
316           if(strcmp($this->item[0], "SIEVE") == 0){
317             $this->cap_type="modules";
318           }else{
319             $this->cap_type="auth";
320           }
322           $this->modules = explode(" ", $this->item[1]);
323           if(is_array($this->modules)){
324             foreach($this->modules as $this->module)
325               $this->capabilities[$this->cap_type][$this->module]=true;
326           } /* end if */
327           elseif(is_string($this->modules))
328             $this->capabilites[$this->cap_type][$this->modules]=true;
329         }    
330         else{ 
331           $this->capabilities["unknown"][]=$this->line;
332         }    
333         $this->line=fgets($this->fp,1024);
335       }// end while
336     }
337     else
338     {
339       //we're on the older Cyrus V1. server  
340       //this version does not support module reporting.  We only have auth types.
341       $this->cap_type="auth";
343       //break apart at the "Cyrus timsieve...." "SASL={......}"
344       $this->item = sieve::parse_for_quotes($this->line);
346       $this->capabilities["implementation"] = $this->item[0];
348       //we should have "SASL={..........}" now.  Break out the {xx,yyy,zzzz}
349       $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
351       //then split again at the ", " stuff.
352       $this->modules = explode(", ", $this->modules);
354       //fill up our $this->modules property
355       if(is_array($this->modules)){
356         foreach($this->modules as $this->module)
357           $this->capabilities[$this->cap_type][$this->module]=true;
358       } /* end if */
359       elseif(is_string($this->modules))
360         $this->capabilites[$this->cap_type][$this->module]=true;
361     }
366     if(sieve::status($this->line) == F_NO){             //here we should do some returning of error codes?
367       $this->error=EC_UNKNOWN;
369       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,$this->error_raw,
370           "<b>SIEVE: Unknown sieve response, giving up.</b>");
372       $this->error_raw = "Server not allowing connections.";
373       return false;
374     }
376     /* Loop through each allowed authentication type and see if the server allows the type */
377     foreach(explode(" ",$this->auth_types) as $auth_type)
378     {
379       if (isset($this->capabilities["auth"][$auth_type]))
380       {
381         /* We found an auth type that is allowed. */
382         $this->auth_in_use = $auth_type;
383         break;
384       }
385     }
387     /* Sometimes we do not get any authentification methods, assume PLAIN here.
388      * #TODO Maybe there is a better solution for this?
389      */
390     if($this->auth_in_use == ""){
391       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,$this->error_raw,
392           "<b>SIEVE: No authentification mechanisms received, try using PLAIN!.</b>");
393         $this->auth_in_use  = "PLAIN";
394     }
396     /* Fill error message if no auth types are present */
397     if (($this->options != "tls") && (!isset($this->capabilities["auth"]))){
398       $this->error=EC_UNKNOWN;
399       $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules";
400       return false;
401     }
403     /* DEBUG */    
404     $imp = "";
405     if(isset($this->capabilities['implementation'])){
406       $imp = $this->capabilities['implementation'];
407     }
408     @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,$imp,
409         "<b>SIEVE: Socket connection established. </b>");
411     /* call our authentication program */  
412     return sieve::authenticate();
414   }
416   function sieve_logout()
417   {
418     if($this->loggedin==false)
419       return false;
421     fputs($this->fp,"LOGOUT\r\n");
422     fclose($this->fp);
424     @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"",
425         "<b>SIEVE: Logout!</b>");
427     $this->loggedin=false;
428     return true;
429   }
431   function sieve_sendscript($scriptname, $script)
432   {
433     if($this->loggedin==false)
434       return false;
435     $this->script=stripslashes($script);
436     $len=strlen($this->script);
437     fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n");
438     fputs($this->fp, "$this->script\r\n");
440     if(sieve::get_response()){
441       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"",
442           "<b>SIEVE: Script '".$scriptname."' successfully send!</b>");
443       return(TRUE);
444     }else{
445       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,trim($this->error_raw),
446           "<b>SIEVE: Script couldn't be send!</b>");
447       return(FALSE);
448     }
449   }  
451   //it appears the timsieved does not honor the NUMBER type.  see lex.c in timsieved src.
452   //don't expect this function to work yet.  I might have messed something up here, too.
453   function sieve_havespace($scriptname, $scriptsize)
454   {
455     if($this->loggedin==false)
456       return false;
457     fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
458     return sieve::get_response();
460   }  
462   function sieve_setactivescript($scriptname)
463   {
464     if($this->loggedin==false)
465       return false;
467     fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");  
469     if(sieve::get_response()){
470       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"",
471           "<b>SIEVE: Set active script '".$scriptname."' was successful!</b>");
472       return(TRUE);
473     }else{
474       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,trim($this->error_raw),
475           "<b>SIEVE: Set active script '".$scriptname."' failed!</b>");
476       return(FALSE);
477     }
478   }
480   function sieve_getscript($scriptname)
481   {
482     unset($this->script);
483     if($this->loggedin==false)
484       return false;
486     fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
488     if(sieve::get_response()){
489       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"",
490           "<b>SIEVE: Get script '".$scriptname."' was successful!</b>");
491       return(TRUE);
492     }else{
493       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,trim($this->error_raw),
494           "<b>SIEVE: Get script '".$scriptname."' failed!</b>");
495       return(FALSE);
496     }
497   }
500   function sieve_deletescript($scriptname)
501   {
502     if($this->loggedin==false)
503       return false;
505     fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");    
507     if(sieve::get_response()){
508       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"",
509           "<b>SIEVE: Delete script '".$scriptname."' was successful!</b>");
510       return(TRUE);
511     }else{
512       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,trim($this->error_raw),
513           "<b>SIEVE: Delete  script '".$scriptname."' failed!</b>");
514       return(FALSE);
515     }
516   }
519   function sieve_listscripts() 
520   { 
521     fputs($this->fp, "LISTSCRIPTS\r\n"); 
522     sieve::get_response();              //should always return true, even if there are no scripts...
523     if(isset($this->found_script) and $this->found_script){
524       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__, implode($this->response,", "),
525           "<b>SIEVE: List scripts was successful!</b>");
526       return true;
527     }else{
528       $this->error=EC_NOSCRIPTS;        //sieve::getresponse has no way of telling wether a script was found...
529       $this->error_raw="No scripts found for this account.";
530       @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__, $this->error_raw,
531           "<b>SIEVE: List scripts returned no scripts</b>");
532       return false;
533     }
534   }
536   function sieve_alive()
537   {
538     if(!isset($this->fp) or $this->fp==0){
539       $this->error = EC_NOT_LOGGED_IN;
540       return false;
541     }
542     elseif(feof($this->fp)){                    
543       $this->error = EC_NOT_LOGGED_IN;
544       return false;
545     }
546     else
547       return true;
548   }
551   function authenticate()
552   {
554     /* Check if a TLS bases connection is required 
555      */
556     if($this->options == "tls"){
558       /* Check if PHP supports TLS encryption for sockets 
559        */
560       if(function_exists("stream_socket_enable_crypto")){
562         /* Initiate TLS and get response 
563          */
564         if (@ fputs ($this->fp, "STARTTLS\r\n") === false) {
565           @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"couldn't send data to socket.",
566               "<b>SIEVE: TLS couldn't be initiated. </b>");
567           $this->error_raw = "fputs() returned 'false'"; return (false);
568         } 
569         @ $linha = fgets ($this->fp, 1024); 
570         if ($linha === false) {
571           $this->error_raw = "fgets() returned 'false'"; 
572           @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"couldn't read data from socket.",
573               "<b>SIEVE: TLS couldn't be initiated. </b>");
574           return (false);
575         }
576         if (! preg_match ("/\\s*OK\\s/i", $linha)) {
577           @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"server returned '".trim ($linha)."' instead of 'OK'",
578               "<b>SIEVE: TLS couldn't be initiated. </b>");
579           $this->error_raw = "expected an 'OK', but server replied: '" . trim ($linha) . "'"; 
580           return (false);
581         } 
582         if (! @ stream_socket_enable_crypto ($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
583           @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,
584               "The socket doesn't seem to support STREAM_CRYPTO_METHOD_TLS_CLIENT",
585               "<b>SIEVE: TLS couldn't be initiated. </b>");
586           $this->error_raw = "stream_socket_enable_crypto() returned 'false'"; 
587           return (false);
588         }
589         @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"",
590             "<b>SIEVE: TLS successfully initiated. </b>");
591       }
592     }
594     switch ($this->auth_in_use) {
596       case "PLAIN":
597       {
598         $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
600         $this->len=strlen($auth);                       
601         fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n");
602         fputs($this->fp, "$auth\r\n");
604         $this->line=fgets($this->fp,1024);              
605         while(sieve::status($this->line) == F_DATA)
606           $this->line=fgets($this->fp,1024);
608         if(sieve::status($this->line) == F_NO){
609           $this->error_raw = $this->line;
610           @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,trim($this->error_raw),
611               "<b>SIEVE: Authentication for '".$this->user."-".$this->auth_in_use."' failed.</b>");
613           return false;
614         }
616         @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"",
617             "<b>SIEVE: Authentication for '".$this->user."-".$this->auth_in_use."' was successful.</b>");
618         $this->loggedin=true;
619         return true;    
620       }break;
622       default:
623       {
624         @DEBUG (DEBUG_MAIL, __LINE__, __FUNCTION__, __FILE__,"Unsupported authentication method '".$this->auth_in_use."'!",
625             "<b>SIEVE: Authentication for '".$this->user."' with type '".$this->auth_in_use."' failed.</b>");
627         $this->error_raw = "Unsupported authentication method '".$this->auth_in_use."'!";
628         return false;
629       }   break;
631     }//end switch
634   }//end authenticate()
640 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
641 ?>