source: trunk/expressoAdmin1_2/inc/sieve-php.lib.php @ 30

Revision 30, 30.0 KB checked in by niltonneto, 14 years ago (diff)

* empty log message *

  • Property svn:eol-style set to native
  • Property svn:executable set to *
Line 
1<?php
2/**
3 * sieve-php.lib.php
4 *
5 * $Id$
6 *
7 * Copyright 2001-2003 Dan Ellis <danellis@rushmore.com>
8 *
9 * This program is released under the GNU Public License.  See the enclosed
10 * file COPYING for license information. If you did not receive this file, see
11 * http://www.fsf.org/copyleft/gpl.html.
12 *
13 * You should have received a copy of the GNU Public License along with this
14 * package; if not, write to the Free Software Foundation, Inc., 59 Temple
15 * Place - Suite 330, Boston, MA 02111-1307, USA.       
16 *
17 * See CHANGES for updates since last release
18 *
19 * @author Dan Ellis
20 * @package sieve-php
21 * @copyright Copyright 2002-2003, Dan Ellis, All Rights Reserved. 
22 * @version 0.1.0
23 */
24
25/**
26 * Constants
27 */
28define ("F_NO", 0);             
29define ("F_OK", 1);
30define ("F_DATA", 2);
31define ("F_HEAD", 3);
32
33define ("EC_NOT_LOGGED_IN", 0);
34define ("EC_QUOTA", 10);
35define ("EC_NOSCRIPTS", 20);
36define ("EC_UNKNOWN", 255);
37
38/**
39 * SIEVE class - A Class that implements MANAGESIEVE in PHP4|5.
40 *
41 * This program provides a handy interface into the Cyrus timsieved server
42 * under php4.  It is tested with Sieve server included in Cyrus 2.0, but it
43 * has been upgraded (not tested) to work with older Sieve server versions.
44 *
45 * All functions will return either true or false and will fill in
46 * $sieve->error with a defined error code like EC_QUOTA, raw server errors in
47 * $sieve->error_raw, and successful responses in $sieve->responses.
48 *
49 * NOTE: a major change since version (0.0.5) is the inclusion of a standard
50 * method to retrieve  server responses.  All functions will return either true
51 * or false and will fill in $sieve->error with a defined error code like
52 * EC_QUOTA, raw server errors in $sieve->error_raw, and successful responses
53 * in $sieve->responses.
54 *
55 * Usage is pretty simple.  The basics is login, do what you need and logout.
56 * There are two sample files (which suck) test.php and testsieve.php.
57 * test.php allows you to create/delete/view scripts and testsieve.php is a
58 * very basic sieve server test.
59 *
60 * Please let us know of any bugs, problems or ideas at sieve-php development
61 * list:  sieve-php-devel@lists.sourceforge.net. A web interface to subscribe
62 * to this list is available at:
63 * https://lists.sourceforge.net/mailman/listinfo/sieve-php-devel
64 *
65 * @author Dan Ellis
66 * @example simple_example.php A simple example that shows usage of sieve-php
67 * class.
68 * @example vacationset-sieve.php A more elaborate example of vacation script
69 * handling.
70 * @version 0.1.0
71 * @package sieve-php
72 * @todo Maybe add the NOOP function.
73 * @todo Have timing mechanism when port problems arise.
74 * @todo Provide better error diagnostics.
75 */
76class sieve {
77  var $host;
78  var $port;
79  var $user;
80  var $pass;
81  /**
82   * a comma seperated list of allowed auth types, in order of preference
83   */
84  var $auth_types;
85  /**
86   * type of authentication attempted
87   */
88  var $auth_in_use;
89 
90  var $line;
91  var $fp;
92  var $retval;
93  var $tmpfile;
94  var $fh;
95  var $len;
96  var $script;
97
98  var $loggedin;
99  var $capabilities;
100  var $error;
101  var $error_raw;
102  var $responses;
103
104  //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN)
105  //so we can decide how to handle certain errors?!?
106
107  /**
108   * get response
109   */
110  function get_response()
111  {
112    if($this->loggedin == false or feof($this->fp)){
113        $this->error = EC_NOT_LOGGED_IN;
114        $this->error_raw = "You are not logged in.";
115        return false;
116    }
117
118    unset($this->response);
119    unset($this->error);
120    unset($this->error_raw);
121
122    $this->line=fgets($this->fp,1024);
123    $this->token = split(" ", $this->line, 2);
124
125    if($this->token[0] == "NO"){
126        /* we need to try and extract the error code from here.  There are two possibilites: one, that it will take the form of:
127           NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */
128        $this->x = 0;
129        list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3);
130        if($this->mtoken[0] == "{"){
131            while($this->mtoken[$this->x] != "}" or $this->err_len < 1){
132                $this->err_len = substr($this->mtoken, 1, $this->x);
133                $this->x++;   
134            }
135            //print "<br>Trying to receive $this->err_len bytes for result<br>";
136            $this->line = fgets($this->fp,$this->err_len);
137            $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2);    //we want to be nice and strip crlf's
138            $this->err_recv = strlen($this->line);
139
140            while($this->err_recv < $this->err_len){
141                //print "<br>Trying to receive ".($this->err_len-$this->err_recv)." bytes for result<br>";
142                $this->line = fgets($this->fp, ($this->err_len-$this->err_recv));
143                $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2);    //we want to be nice and strip crlf's
144                $this->err_recv += strlen($this->line);
145            } /* end while */
146            $this->line = fgets($this->fp, 1024);       //we need to grab the last crlf, i think.  this may be a bug...
147            $this->error=EC_UNKNOWN;
148     
149        } /* end if */
150        elseif($this->mtoken[0] == "("){
151            switch($this->mtoken){
152                case "(\"QUOTA\")":
153                    $this->error = EC_QUOTA;
154                    $this->error_raw=$this->rtoken;
155                    break;
156                default:
157                    $this->error = EC_UNKNOWN;
158                    $this->error_raw=$this->rtoken;
159                    break;
160            } /* end switch */
161        } /* end elseif */
162        else{
163            $this->error = EC_UNKNOWN;
164            $this->error_raw = $this->line;
165        }     
166        return false;
167
168    } /* end if */
169    elseif(substr($this->token[0],0,2) == "OK"){
170         return true;
171    } /* end elseif */
172    elseif($this->token[0][0] == "{"){
173       
174        /* Unable wild assumption:  that the only function that gets here is the get_script(), doesn't really matter though */       
175
176        /* the first line is the len field {xx}, which we don't care about at this point */
177        $this->line = fgets($this->fp,1024);
178        while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
179            $this->response[]=$this->line;
180            $this->line = fgets($this->fp, 1024);
181        }
182        if(substr($this->line,0,2) == "OK")
183            return true;
184        else
185            return false;
186    } /* end elseif */
187    elseif($this->token[0][0] == "\""){
188
189        /* I'm going under the _assumption_ that the only function that will get here is the listscripts().
190           I could very well be mistaken here, if I am, this part needs some rework */
191
192        $this->found_script=false;       
193
194        while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
195            $this->found_script=true;
196            list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
197                //hmmm, a bug in php, if there is no space on explode line, a warning is generated...
198           
199            if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
200                $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1); 
201            }
202            else
203                $this->response[] = substr(rtrim($this->ltoken),1,-1);
204            $this->line = fgets($this->fp, 1024);
205        } /* end while */
206       
207        return true;
208       
209    } /* end elseif */
210    else{
211            $this->error = EC_UNKNOWN;
212            $this->error_raw = $this->line;
213            print '<b><i>UNKNOWN ERROR (Please report this line to <a
214            href="mailto:sieve-php-devel@lists.sourceforge.net">sieve-php-devel
215            Mailing List</a> to include in future releases):
216            '.$this->line.'</i></b><br>';
217
218            return false;
219    } /* end else */   
220  } /* end get_response() */
221
222  /**
223   * Initialization of the SIEVE class.
224   *
225   * It will return
226   * false if it fails, true if all is well.  This also loads some arrays up
227   * with some handy information:
228   *
229   * @param $host string hostname to connect to. Usually the IMAP server where
230   * a SIEVE daemon, such as timsieved, is listening.
231   *
232   * @param $port string Numeric port to connect to. SIEVE daemons usually
233   * listen to port 2000.
234   *
235   * @param $user string is a super-user or proxy-user that has ACL rights to
236   * login on behalf of the $auth.
237   *
238   * @param $pass string password to use for authentication
239   *
240   * @param $auth string is the authorized user identity for which the SIEVE
241   * scripts will be managed.
242   *
243   * @param $auth_types string a string containing all the allowed
244   * authentication types allowed in order of preference, seperated by spaces.
245   * (ex.  "PLAIN DIGEST-MD5 CRAM-MD5"  The method the library will try first
246   * is PLAIN.) The default for this value is PLAIN.
247   *
248   * Note: $user, if included, is the account name (and $pass will be the
249   * password) of an administrator account that can act on behalf of the user.
250   * If you are using Cyrus, you must make sure that the admin account has
251   * rights to admin the user.  This is to allow admins to edit/view users
252   * scripts without having to know the user's password.  Very handy.
253   */
254  function sieve($host, $port, $user, $pass, $auth="", $auth_types='PLAIN') {
255    $this->host=$host;
256    $this->port=$port;
257    $this->user=$user;
258    $this->pass=$pass;
259    if(!strcmp($auth, ""))              /* If there is no auth user, we deem the user itself to be the auth'd user */
260        $this->auth = $this->user;
261    else
262        $this->auth = $auth;
263    $this->auth_types=$auth_types;      /* Allowed authentication types */
264    $this->fp=0;
265    $this->line="";
266    $this->retval="";
267    $this->tmpfile="";
268    $this->fh=0;
269    $this->len=0;
270    $this->capabilities="";
271    $this->loggedin=false;
272    $this->error= "";
273    $this->error_raw="";
274  }
275
276   /**
277    * Tokenize a line of input by quote marks and return them as an array
278    *
279    * @param $string string Input line to parse for quotes
280    * @return array Array of broken by quotes parts of original string
281    */
282  function parse_for_quotes($string) {
283
284      $start = -1;
285      $index = 0;
286
287      for($ptr = 0; $ptr < strlen($string); $ptr++){
288          if($string[$ptr] == '"' and $string[$ptr] != '\\'){
289              if($start == -1){
290                  $start = $ptr;
291              } /* end if */
292              else{
293                  $token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
294                  $found = true;
295                  $start = -1;
296              } /* end else */
297
298          } /* end if */ 
299
300      } /* end for */
301
302      if(isset($token))
303          return $token;
304      else
305          return false;
306  } /* end function */           
307
308  /**
309   * Parser for status responses.
310   *
311   * This should probably be replaced by a smarter parser.
312   *
313   * @param $string string Input that contains status responses.
314   * @todo remove this function and dependencies
315   */
316  function status($string) {
317
318      /*  Need to remove this and all dependencies from the class */
319
320      switch (substr($string, 0,2)){
321          case "NO":
322              return F_NO;              //there should be some function to extract the error code from this line
323                                        //NO ("quota") "You are oly allowed x number of scripts"
324              break;
325          case "OK":
326              return F_OK;
327              break;
328          default:
329              switch ($string[0]){
330                  case "{":
331                      //do parse here for curly braces - maybe modify
332                      //parse_for_quotes to handle any parse delimiter?
333                      return F_HEAD;
334                      break;
335                  default:
336                      return F_DATA;
337                      break;
338              }
339        }
340  }
341 
342  /**
343   * Attemp to log in to the sieve server.
344   *
345   * It will return false if it fails, true if all is well.  This also loads
346   * some arrays up with some handy information:
347   *
348   * capabilities["implementation"] contains the sieve version information
349   *
350   * capabilities["auth"] contains the supported authentication modes by the
351   * SIEVE server.
352   *
353   * capabilities["modules"] contains the built in modules like "reject",
354   * "redirect", etc.
355   *
356   * capabilities["starttls"] , if is set and equal to true, will show that the
357   * server supports the STARTTLS extension.
358   *
359   * capabilities["unknown"] contains miscellaneous/extraneous header info sieve
360   * may have sent
361   *
362   * @return boolean
363   */
364  function sieve_login() {
365    $this->fp=fsockopen($this->host,$this->port);
366    if($this->fp == false)
367        return false;
368
369    $this->line=fgets($this->fp,1024);
370    //Hack for older versions of Sieve Server.  They do not respond with the Cyrus v2. standard
371    //response.  They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
372    //So, if we see IMLEMENTATION in the first line, then we are done.
373
374    if(ereg("IMPLEMENTATION",$this->line))
375    {
376      //we're on the Cyrus V2 sieve server
377      while(sieve::status($this->line) == F_DATA){
378
379          $this->item = sieve::parse_for_quotes($this->line);
380
381          if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
382              $this->capabilities["implementation"] = $this->item[1];
383       
384          elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
385
386              if(strcmp($this->item[0], "SIEVE") == 0)
387                  $this->cap_type="modules";
388              else
389                  $this->cap_type="auth";           
390
391              $this->modules = split(" ", $this->item[1]);
392              if(is_array($this->modules)){
393                  foreach($this->modules as $this->module)
394                      $this->capabilities[$this->cap_type][$this->module]=true;
395              } /* end if */
396              elseif(is_string($this->modules))
397                  $this->capabilites[$this->cap_type][$this->modules]=true;
398          }   
399          elseif(strcmp($this->item[0], "STARTTLS") == 0) {
400                  $this->capabilities['starttls'] = true;
401         
402          }
403          else{
404              $this->capabilities["unknown"][]=$this->line;
405          }   
406      $this->line=fgets($this->fp,1024);
407       }// end while
408    }
409    else
410    {
411        //we're on the older Cyrus V1. server 
412        //this version does not support module reporting.  We only have auth types.
413        $this->cap_type="auth";
414       
415        //break apart at the "Cyrus timsieve...." "SASL={......}"
416        $this->item = sieve::parse_for_quotes($this->line);
417
418        $this->capabilities["implementation"] = $this->item[0];
419
420        //we should have "SASL={..........}" now.  Break out the {xx,yyy,zzzz}
421        $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
422
423        //then split again at the ", " stuff.
424        $this->modules = split($this->modules, ", ");
425 
426        //fill up our $this->modules property
427        if(is_array($this->modules)){
428            foreach($this->modules as $this->module)
429                $this->capabilities[$this->cap_type][$this->module]=true;
430        } /* end if */
431        elseif(is_string($this->modules))
432            $this->capabilites[$this->cap_type][$this->module]=true;
433    }
434
435
436
437
438    if(sieve::status($this->line) == F_NO){             //here we should do some returning of error codes?
439        $this->error=EC_UNKNOWN;
440        $this->error_raw = "Server not allowing connections.";
441        return false;
442    }
443
444    /* decision login to decide what type of authentication to use... */
445
446
447     /* Loop through each allowed authentication type and see if the server allows the type */
448     foreach(explode(" ", $this->auth_types) as $auth_type)
449     {
450        if ($this->capabilities["auth"][$auth_type])
451        {
452            /* We found an auth type that is allowed. */
453            $this->auth_in_use = $auth_type;
454        }
455     }
456   
457     /* call our authentication program */
458   
459    return sieve::authenticate();
460
461  }
462
463  /**
464   * Log out of the sieve server.
465   *
466   * @return boolean Always returns true at this point.
467   */
468  function sieve_logout() {
469    if($this->loggedin==false)
470        return false;
471
472    fputs($this->fp,"LOGOUT\r\n");
473    fclose($this->fp);
474    $this->loggedin=false;
475    return true;
476  }
477
478  /**
479   * Send the script contained in $script to the server.
480   *
481   * It will return any error results it finds (in $sieve->error and
482   * $sieve->error_raw), and return true if it is successfully sent.  The
483   * function does _not_ automatically make the script the active script.
484   *
485   * @param $scriptname string The name of the SIEVE script.
486   * @param $script The script to be uploaded.
487   * @return boolean Returns true if script has been successfully uploaded.
488   */
489  function sieve_sendscript($scriptname, $script) {
490    if($this->loggedin==false)
491        return false;
492    $this->script=stripslashes($script);
493    $len=strlen($this->script);
494   
495    //fputs($this->fp, "PUTSCRIPT \"$scriptname\" \{$len+}\r\n");
496   
497    fputs($this->fp, 'PUTSCRIPT "' . $scriptname . '" {' . $len . '+}' . "\r\n");
498    fputs($this->fp, "$this->script\r\n");
499 
500    return sieve::get_response();
501
502  } 
503 
504  /**
505   * Check if there is enough space for a script to be uploaded.
506   *
507   * This function returns true or false based on whether the sieve server will
508   * allow your script to be sent and your quota has not been exceeded.  This
509   * function does not currently work due to a believed bug in timsieved.  It
510   * could be my code too.
511   *
512   * It appears the timsieved does not honor the NUMBER type.  see lex.c in
513   * timsieved src.  don't expect this function to work yet.  I might have
514   * messed something up here, too.
515   *
516   * @param $scriptname string The name of the SIEVE script.
517   * @param $scriptsize integer The size of the SIEVE script.
518   * @return boolean
519   * @todo Does not work; bug fix and test.
520   */
521  function sieve_havespace($scriptname, $scriptsize)   {
522    if($this->loggedin==false)
523        return false;
524    fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
525    return sieve::get_response();
526  } 
527
528  /**
529   * Set the script active on the sieve server.
530   *
531   * @param $scriptname string The name of the SIEVE script.
532   * @return boolean
533   */
534  function sieve_setactivescript($scriptname)   {
535    if($this->loggedin==false)
536        return false;
537
538    fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");   
539    return sieve::get_response();
540
541  }
542 
543  /**
544   * Return the contents of the requested script.
545   *
546   * If you want to display the script, you will need to change all CrLf to
547   * '.'.
548   *
549   * @param $scriptname string The name of the SIEVE script.
550   * @return arr SIEVE script data.
551   */
552  function sieve_getscript($scriptname) {
553    unset($this->script);
554    if($this->loggedin==false)
555        return false;
556
557    fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
558    return sieve::get_response();
559  }
560
561  /**
562   * Attempt to delete the script requested.
563   *
564   * If the script is currently active, the server will not have any active
565   * script after the deletion.
566   *
567   * @param $scriptname string The name of the SIEVE script.
568   * @return mixed
569   */
570  function sieve_deletescript($scriptname)   {
571    if($this->loggedin==false)
572        return false;
573
574    fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");   
575
576    return sieve::get_response();
577  }
578
579 
580  /**
581   * List available scripts on the SIEVE server.
582   *
583   * This function returns true or false.  $sieve->response will be filled
584   * with the names of the scripts found.  If a script is active, the
585   * $sieve->response["ACTIVE"] will contain the name of the active script.
586   *
587   * @return boolean
588   */
589  function sieve_listscripts() {
590     fputs($this->fp, "LISTSCRIPTS\r\n");
591     sieve::get_response();             //should always return true, even if there are no scripts...
592     if(isset($this->found_script) and $this->found_script)
593         return true;
594     else{
595         $this->error=EC_NOSCRIPTS;     //sieve::getresponse has no way of telling wether a script was found...
596         $this->error_raw="No scripts found for this account.";
597         return false;
598     }
599   }
600
601
602  /**
603   * Check availability of connection to the SIEVE server.
604   *
605   * This function returns true or false based on whether the connection to the
606   * sieve server is still alive.
607   *
608   * @return boolean
609   */
610  function sieve_alive()   {
611      if(!isset($this->fp) or $this->fp==0){
612          $this->error = EC_NOT_LOGGED_IN;
613          return false;
614      }
615      elseif(feof($this->fp)){                 
616          $this->error = EC_NOT_LOGGED_IN;
617          return false;
618      }
619      else
620          return true;
621  }
622
623  /**
624   * Perform SASL authentication to SIEVE server.
625   *
626   * Attempts to authenticate to SIEVE, using some SASL authentication method
627   * such as PLAIN or DIGEST-MD5.
628   *
629   */
630  function authenticate() {
631    switch ($this->auth_in_use) {
632
633        case "PLAIN":
634            $auth=base64_encode("$this->auth\0$this->user\0$this->pass");
635            $this->len=strlen($auth);
636                       
637            fputs($this->fp, 'AUTHENTICATE "PLAIN" {' . $this->len . '+}' . "\r\n");
638            fputs($this->fp, "$auth\r\n");
639
640            $this->line=fgets($this->fp,1024);
641            while(sieve::status($this->line) == F_DATA)
642               $this->line=fgets($this->fp,1024);
643
644             if(sieve::status($this->line) == F_NO)
645               return false;
646             $this->loggedin=true;
647               return true;   
648            break;
649       
650        case "DIGEST-MD5":
651             // SASL DIGEST-MD5 support works with timsieved 1.1.0
652             // follows rfc2831 for generating the $response to $challenge
653             fputs($this->fp, "AUTHENTICATE \"DIGEST-MD5\"\r\n");
654             // $clen is length of server challenge, we ignore it.
655             $clen = fgets($this->fp, 1024);
656             // read for 2048, rfc2831 max length allowed
657             $challenge = fgets($this->fp, 2048);
658             // vars used when building $response_value and $response
659             $cnonce = base64_encode(bin2hex(hmac_md5(microtime())));
660             $ncount = "00000001";
661             $qop_value = "auth";
662             $digest_uri_value = "sieve/$this->host";
663             // decode the challenge string
664             $result = decode_challenge($challenge);
665             // verify server supports qop=auth
666             $qop = explode(",",$result['qop']);
667             if (!in_array($qop_value, $qop)) {
668                // rfc2831: client MUST fail if no qop methods supported
669                return false;
670             }
671             // build the $response_value
672             $string_a1 = utf8_encode($this->user).":";
673             $string_a1 .= utf8_encode($result['realm']).":";
674             $string_a1 .= utf8_encode($this->pass);
675             $string_a1 = hmac_md5($string_a1);
676             $A1 = $string_a1.":".$result['nonce'].":".$cnonce.":".utf8_encode($this->auth);
677             $A1 = bin2hex(hmac_md5($A1));
678             $A2 = bin2hex(hmac_md5("AUTHENTICATE:$digest_uri_value"));
679             $string_response = $result['nonce'].":".$ncount.":".$cnonce.":".$qop_value;
680             $response_value = bin2hex(hmac_md5($A1.":".$string_response.":".$A2));
681             // build the challenge $response
682             $reply = "charset=utf-8,username=\"".$this->user."\",realm=\"".$result['realm']."\",";
683             $reply .= "nonce=\"".$result['nonce']."\",nc=$ncount,cnonce=\"$cnonce\",";
684             $reply .= "digest-uri=\"$digest_uri_value\",response=$response_value,";
685             $reply .= "qop=$qop_value,authzid=\"".utf8_encode($this->auth)."\"";
686             $response = base64_encode($reply);
687             fputs($this->fp, "\"$response\"\r\n");
688       
689             $this->line = fgets($this->fp, 1024);
690             while(sieve::status($this->line) == F_DATA)
691                $this->line = fgets($this->fp,1024);
692
693             if(sieve::status($this->line) == F_NO)
694               return false;
695             $this->loggedin = TRUE;
696               return TRUE;   
697             break;
698       
699        case "CRAM-MD5":
700             // SASL CRAM-MD5 support works with timsieved 1.1.0
701             // follows rfc2195 for generating the $response to $challenge
702             // CRAM-MD5 does not support proxy of $auth by $user
703             // requires php mhash extension
704             fputs($this->fp, "AUTHENTICATE \"CRAM-MD5\"\r\n");
705             // $clen is the length of the challenge line the server gives us
706             $clen = fgets($this->fp, 1024);
707             // read for 1024, should be long enough?
708             $challenge = fgets($this->fp, 1024);
709             // build a response to the challenge
710             $hash = bin2hex(hmac_md5(base64_decode($challenge), $this->pass));
711             $response = base64_encode($this->user." ".$hash);
712             // respond to the challenge string
713             fputs($this->fp, "\"$response\"\r\n");
714             
715             $this->line = fgets($this->fp, 1024);             
716             while(sieve::status($this->line) == F_DATA)
717                $this->line = fgets($this->fp,1024);
718
719             if(sieve::status($this->line) == F_NO)
720               return false;
721             $this->loggedin = TRUE;
722               return TRUE;   
723             break;
724
725        case "LOGIN":
726             $login=base64_encode($this->user);
727             $pass=base64_encode($this->pass);
728       
729             fputs($this->fp, "AUTHENTICATE \"LOGIN\"\r\n");
730             fputs($this->fp, "{".strlen($login)."+}\r\n");
731             fputs($this->fp, "$login\r\n");
732             fputs($this->fp, "{".strlen($pass)."+}\r\n");
733             fputs($this->fp, "$pass\r\n");
734 
735             $this->line=fgets($this->fp,1024);
736             while(sieve::status($this->line) == F_HEAD ||
737                   sieve::status($this->line) == F_DATA)
738                 $this->line=fgets($this->fp,1024);
739       
740             if(sieve::status($this->line) == F_NO)
741                 return false;
742             $this->loggedin=true;
743             return true;
744             break;
745
746        default:
747            echo 'default';
748            return false;
749            break;
750
751    }//end switch
752  }
753
754  /**
755   * Return an array of available capabilities.
756   *
757   * @return array
758   */
759  function sieve_get_capability() {
760    if($this->loggedin==false)
761        return false;
762    fputs($this->fp, "CAPABILITY\r\n");
763    $this->line=fgets($this->fp,1024);
764
765    //Hack for older versions of Sieve Server.  They do not respond with the Cyrus v2. standard
766    //response.  They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
767    //So, if we see IMLEMENTATION in the first line, then we are done.
768
769    if(ereg("IMPLEMENTATION",$this->line))
770    {
771      //we're on the Cyrus V2 sieve server
772      while(sieve::status($this->line) == F_DATA){
773
774          $this->item = sieve::parse_for_quotes($this->line);
775
776          if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
777              $this->capabilities["implementation"] = $this->item[1];
778       
779          elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
780
781              if(strcmp($this->item[0], "SIEVE") == 0)
782                  $this->cap_type="modules";
783              else
784                  $this->cap_type="auth";           
785
786              $this->modules = split(" ", $this->item[1]);
787              if(is_array($this->modules)){
788                  foreach($this->modules as $this->module)
789                      $this->capabilities[$this->cap_type][$this->module]=true;
790              } /* end if */
791              elseif(is_string($this->modules))
792                  $this->capabilites[$this->cap_type][$this->modules]=true;
793          }   
794          else{
795              $this->capabilities["unknown"][]=$this->line;
796          }   
797      $this->line=fgets($this->fp,1024);
798
799       }// end while
800    }
801    else
802    {
803        //we're on the older Cyrus V1. server 
804        //this version does not support module reporting.  We only have auth types.
805        $this->cap_type="auth";
806       
807        //break apart at the "Cyrus timsieve...." "SASL={......}"
808        $this->item = sieve::parse_for_quotes($this->line);
809
810        $this->capabilities["implementation"] = $this->item[0];
811
812        //we should have "SASL={..........}" now.  Break out the {xx,yyy,zzzz}
813        $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
814
815        //then split again at the ", " stuff.
816        $this->modules = split($this->modules, ", ");
817 
818        //fill up our $this->modules property
819        if(is_array($this->modules)){
820            foreach($this->modules as $this->module)
821                $this->capabilities[$this->cap_type][$this->module]=true;
822        } /* end if */
823        elseif(is_string($this->modules))
824            $this->capabilites[$this->cap_type][$this->module]=true;
825    }
826
827    return $this->modules;
828  }
829
830}
831
832
833/**
834 * The following functions are support functions and might be handy to the
835 * sieve class.
836 */
837
838if(!function_exists('hmac_md5')) {
839
840/**
841 * Creates a HMAC digest that can be used for auth purposes.
842 * See RFCs 2104, 2617, 2831
843 * Uses mhash() extension if available
844 *
845 * Squirrelmail has this function in functions/auth.php, and it might have been
846 * included already. However, it helps remove the dependancy on mhash.so PHP
847 * extension, for some sites. If mhash.so _is_ available, it is used for its
848 * speed.
849 *
850 * This function is Copyright (c) 1999-2003 The SquirrelMail Project Team
851 * Licensed under the GNU GPL. For full terms see the file COPYING.
852 *
853 * @param string $data Data to apply hash function to.
854 * @param string $key Optional key, which, if supplied, will be used to
855 * calculate data's HMAC.
856 * @return string HMAC Digest string
857 */
858function hmac_md5($data, $key='') {
859    // See RFCs 2104, 2617, 2831
860    // Uses mhash() extension if available
861    if (extension_loaded('mhash')) {
862      if ($key== '') {
863        $mhash=mhash(MHASH_MD5,$data);
864      } else {
865        $mhash=mhash(MHASH_MD5,$data,$key);
866      }
867      return $mhash;
868    }
869    if (!$key) {
870         return pack('H*',md5($data));
871    }
872    $key = str_pad($key,64,chr(0x00));
873    if (strlen($key) > 64) {
874        $key = pack("H*",md5($key));
875    }
876    $k_ipad =  $key ^ str_repeat(chr(0x36), 64) ;
877    $k_opad =  $key ^ str_repeat(chr(0x5c), 64) ;
878    /* Heh, let's get recursive. */
879    $hmac=hmac_md5($k_opad . pack("H*",md5($k_ipad . $data)) );
880    return $hmac;
881}
882}
883
884/**
885 * A hack to decode the challenge from timsieved 1.1.0.
886 *
887 * This function may not work with other versions and most certainly won't work
888 * with other DIGEST-MD5 implentations
889 *
890 * @param $input string Challenge supplied by timsieved.
891 */
892function decode_challenge ($input) {
893    $input = base64_decode($input);
894    preg_match("/nonce=\"(.*)\"/U",$input, $matches);
895    $resp['nonce'] = $matches[1];
896    preg_match("/realm=\"(.*)\"/U",$input, $matches);
897    $resp['realm'] = $matches[1];
898    preg_match("/qop=\"(.*)\"/U",$input, $matches);
899    $resp['qop'] = $matches[1];
900    return $resp;
901}
902
903// vim:ts=4:et:ft=php
904
905?>
Note: See TracBrowser for help on using the repository browser.