1<?
2
3/*
4 * $Id: class.sieve.inc.php 11254 2002-10-16 17:18:24Z lkneschke $
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 */
11
12// TODO before next release:	remove ::status() and dependencies
13
14
15define ("F_NO", 0);
16define ("F_OK", 1);
17define ("F_DATA", 2);
18define ("F_HEAD", 3);
19
20define ("EC_NOT_LOGGED_IN", 0);
21define ("EC_QUOTA", 10);
22define ("EC_NOSCRIPTS", 20);
23define ("EC_UNKNOWN", 255);
24/*
25
26SIEVE-PHP.LIB VERSION 0.0.8
27
28(C) 2001 Dan Ellis.
29
30PLEASE READ THE README FILE FOR MORE INFORMATION.
31
32Basically, this is the first re-release.  Things are much better than before.
33
34Notes:
35This 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 :).
38
39Todo:
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?)
45
46Dan Ellis (danellis@rushmore.com)
47
48This program is released under the GNU Public License.
49
50You 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.
54
55See CHANGES for updates since last release
56
57Contributers of patches:
58	Atif Ghaffar
59	Andrew Sterling Hanenkamp <sterling@hanenkamp.com>
60*/
61
62
63class 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;
79
80  var $loggedin;
81  var $capabilities;
82  var $error;
83  var $error_raw;
84  var $responses;
85
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?!?
88
89  //also add a connection type, like PLAIN, MD5, etc...
90
91
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    }
99
100    unset($this->response);
101    unset($this->error);
102    unset($this->error_raw);
103
104    $this->line=fgets($this->fp,1024);
105    $this->token = split(" ", $this->line, 2);
106
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);
121
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;
149
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 */
157
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] == "\""){
170
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 */
173
174        $this->found_script=false;
175
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() */
199
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  }
222
223  function parse_for_quotes($string)
224  {
225      /* This function tokenizes a line of input by quote marks and returns them as an array */
226
227      $start = -1;
228      $index = 0;
229
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 */
240
241          } /* end if */
242
243      } /* end for */
244
245      if(isset($token))
246          return $token;
247      else
248          return false;
249  } /* end function */
250
251  function status($string)
252  {
253      //this should probably be replaced by a smarter parser.
254
255      /*  Need to remove this and all dependencies from the class */
256
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() */
277
278  function sieve_login()
279  {
280    $this->fp=fsockopen($this->host,$this->port);
281    if($this->fp == false)
282        return false;
283
284
285    $this->line=fgets($this->fp,1024);
286    //Hack for older versions of Sieve Server.  They do not respond with the Cyrus v2. standard
287    //response.  They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
288    //So, if we see IMLEMENTATION in the first line, then we are done.
289
290    if(ereg("IMPLEMENTATION",$this->line))
291    {
292      //we're on the Cyrus V2 sieve server
293      while(sieve::status($this->line) == F_DATA){
294
295          $this->item = sieve::parse_for_quotes($this->line);
296
297          if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
298              $this->capabilities["implementation"] = $this->item[1];
299
300          elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
301
302              if(strcmp($this->item[0], "SIEVE") == 0)
303                  $this->cap_type="modules";
304              else
305                  $this->cap_type="auth";
306
307              $this->modules = split(" ", $this->item[1]);
308              if(is_array($this->modules)){
309                  foreach($this->modules as $this->module)
310                      $this->capabilities[$this->cap_type][$this->module]=true;
311              } /* end if */
312              elseif(is_string($this->modules))
313                  $this->capabilites[$this->cap_type][$this->modules]=true;
314          }
315          else{
316              $this->capabilities["unknown"][]=$this->line;
317          }
318      $this->line=fgets($this->fp,1024);
319
320       }// end while
321    }
322    else
323    {
324        //we're on the older Cyrus V1. server
325        //this version does not support module reporting.  We only have auth types.
326        $this->cap_type="auth";
327
328        //break apart at the "Cyrus timsieve...." "SASL={......}"
329        $this->item = sieve::parse_for_quotes($this->line);
330
331        $this->capabilities["implementation"] = $this->item[0];
332
333        //we should have "SASL={..........}" now.  Break out the {xx,yyy,zzzz}
334        $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
335
336        //then split again at the ", " stuff.
337        $this->modules = split($this->modules, ", ");
338
339        //fill up our $this->modules property
340        if(is_array($this->modules)){
341            foreach($this->modules as $this->module)
342                $this->capabilities[$this->cap_type][$this->module]=true;
343        } /* end if */
344        elseif(is_string($this->modules))
345            $this->capabilites[$this->cap_type][$this->module]=true;
346    }
347
348
349
350
351    if(sieve::status($this->line) == F_NO){		//here we should do some returning of error codes?
352        $this->error=EC_UNKNOWN;
353        $this->error_raw = "Server not allowing connections.";
354        return false;
355    }
356
357    /* decision login to decide what type of authentication to use... */
358
359     /* Loop through each allowed authentication type and see if the server allows the type */
360     foreach(split(" ",$this->auth_types) as $auth_type)
361     {
362        if ($this->capabilities["auth"][$auth_type])
363        {
364            /* We found an auth type that is allowed. */
365            $this->auth_in_use = $auth_type;
366            break;
367        }
368     }
369
370     /* call our authentication program */
371
372    return sieve::authenticate();
373
374  }
375
376  function sieve_logout()
377  {
378    if($this->loggedin==false)
379        return false;
380
381    fputs($this->fp,"LOGOUT\r\n");
382    fclose($this->fp);
383    $this->loggedin=false;
384    return true;
385  }
386
387  function sieve_sendscript($scriptname, $script)
388  {
389    if($this->loggedin==false)
390        return false;
391    $this->script=stripslashes($script);
392    $len=strlen($this->script);
393    fputs($this->fp, "PUTSCRIPT \"$scriptname\" \{$len+}\r\n");
394    fputs($this->fp, "$this->script\r\n");
395
396    return sieve::get_response();
397
398  }
399
400  //it appears the timsieved does not honor the NUMBER type.  see lex.c in timsieved src.
401  //don't expect this function to work yet.  I might have messed something up here, too.
402  function sieve_havespace($scriptname, $scriptsize)
403  {
404    if($this->loggedin==false)
405        return false;
406    fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
407    return sieve::get_response();
408
409  }
410
411  function sieve_setactivescript($scriptname)
412  {
413    if($this->loggedin==false)
414        return false;
415
416    fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");
417    return sieve::get_response();
418
419  }
420
421  function sieve_getscript($scriptname)
422  {
423    unset($this->script);
424    if($this->loggedin==false)
425        return false;
426
427    fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
428    return sieve::get_response();
429
430  }
431
432
433  function sieve_deletescript($scriptname)
434  {
435    if($this->loggedin==false)
436        return false;
437
438    fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");
439
440    return sieve::get_response();
441  }
442
443
444  function sieve_listscripts()
445   {
446     fputs($this->fp, "LISTSCRIPTS\r\n");
447     sieve::get_response();		//should always return true, even if there are no scripts...
448     if(isset($this->found_script) and $this->found_script)
449         return true;
450     else{
451         $this->error=EC_NOSCRIPTS;	//sieve::getresponse has no way of telling wether a script was found...
452         $this->error_raw="No scripts found for this account.";
453         return false;
454     }
455   }
456
457  function sieve_alive()
458  {
459      if(!isset($this->fp) or $this->fp==0){
460          $this->error = EC_NOT_LOGGED_IN;
461          return false;
462      }
463      elseif(feof($this->fp)){
464          $this->error = EC_NOT_LOGGED_IN;
465          return false;
466      }
467      else
468          return true;
469  }
470
471  function authenticate()
472  {
473
474    switch ($this->auth_in_use) {
475
476        case "PLAIN":
477            $auth=base64_encode("$this->user\0$this->auth\0$this->pass");
478
479            $this->len=strlen($auth);
480            fputs($this->fp, "AUTHENTICATE \"PLAIN\" \{$this->len+}\r\n");
481            fputs($this->fp, "$auth\r\n");
482
483            $this->line=fgets($this->fp,1024);
484            while(sieve::status($this->line) == F_DATA)
485               $this->line=fgets($this->fp,1024);
486
487             if(sieve::status($this->line) == F_NO)
488               return false;
489             $this->loggedin=true;
490               return true;
491             break;
492
493        default:
494            return false;
495            break;
496
497    }//end switch
498
499
500  }//end authenticate()
501
502
503}
504
505
506
507?>
508