From: hickert Date: Thu, 15 Feb 2007 11:27:08 +0000 (+0000) Subject: Moved sieve stuff into special folder X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=6fb6d241eee7748e95ffd593bd605f94f6417072;p=gosa.git Moved sieve stuff into special folder git-svn-id: https://oss.gonicus.de/repositories/gosa/trunk@5722 594d385d-05f5-0310-b6e9-bd551577e9d8 --- diff --git a/include/class_sieve.inc b/include/class_sieve.inc deleted file mode 100644 index bdbf7d6fd..000000000 --- a/include/class_sieve.inc +++ /dev/null @@ -1,514 +0,0 @@ - - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - */ - -// TODO before next release: remove ::status() and dependencies - - -define ("F_NO", 0); -define ("F_OK", 1); -define ("F_DATA", 2); -define ("F_HEAD", 3); - -define ("EC_NOT_LOGGED_IN", 0); -define ("EC_QUOTA", 10); -define ("EC_NOSCRIPTS", 20); -define ("EC_UNKNOWN", 255); -/* - -SIEVE-PHP.LIB VERSION 0.0.8 - -(C) 2001 Dan Ellis. - -PLEASE READ THE README FILE FOR MORE INFORMATION. - -Basically, this is the first re-release. Things are much better than before. - -Notes: -This program/libary has bugs. - . This was quickly hacked out, so please let me know what is wrong and if you feel ambitious submit - a patch :). - -Todo: - . Provide better error diagnostics. (mostly done with ver 0.0.5) - . Allow other auth mechanisms besides plain (in progress) - . Have timing mechanism when port problems arise. (not done yet) - . Maybe add the NOOP function. (not done yet) - . Other top secret stuff.... (some done, believe me?) - -Dan Ellis (danellis@rushmore.com) - -This program is released under the GNU Public License. - -You should have received a copy of the GNU Public - License along with this package; if not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. - -See CHANGES for updates since last release - -Contributers of patches: - Atif Ghaffar - Andrew Sterling Hanenkamp -*/ - - -class sieve -{ - var $host; - var $port; - var $user; - var $pass; - var $auth_types; /* a comma seperated list of allowed auth types, in order of preference */ - var $auth_in_use; /* type of authentication attempted */ - - var $line; - var $fp; - var $retval; - var $tmpfile; - var $fh; - var $len; - var $script; - - var $loggedin; - var $capabilities; - var $error; - var $error_raw; - var $responses; - - //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN) - //so we can decide how to handle certain errors?!? - - //also add a connection type, like PLAIN, MD5, etc... - - - function get_response() - { - if($this->loggedin == false or feof($this->fp)){ - $this->error = EC_NOT_LOGGED_IN; - $this->error_raw = "You are not logged in."; - return false; - } - - unset($this->response); - unset($this->error); - unset($this->error_raw); - - $this->line=fgets($this->fp,1024); - $this->token = split(" ", $this->line, 2); - - if($this->token[0] == "NO"){ - /* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of: - NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */ - $this->x = 0; - list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3); - if($this->mtoken[0] == "{"){ - while($this->mtoken[$this->x] != "}" or $this->err_len < 1){ - $this->err_len = substr($this->mtoken, 1, $this->x); - $this->x++; - } - //print "
Trying to receive $this->err_len bytes for result
"; - $this->line = fgets($this->fp,$this->err_len); - $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's - $this->err_recv = strlen($this->line); - - while($this->err_recv < $this->err_len){ - //print "
Trying to receive ".($this->err_len-$this->err_recv)." bytes for result
"; - $this->line = fgets($this->fp, ($this->err_len-$this->err_recv)); - $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's - $this->err_recv += strlen($this->line); - } /* end while */ - $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug... - $this->error=EC_UNKNOWN; - - } /* end if */ - elseif($this->mtoken[0] == "("){ - switch($this->mtoken){ - case "(\"QUOTA\")": - $this->error = EC_QUOTA; - $this->error_raw=$this->rtoken; - break; - default: - $this->error = EC_UNKNOWN; - $this->error_raw=$this->rtoken; - break; - } /* end switch */ - } /* end elseif */ - else{ - $this->error = EC_UNKNOWN; - $this->error_raw = $this->line; - } - return false; - - } /* end if */ - elseif(substr($this->token[0],0,-2) == "OK"){ - return true; - } /* end elseif */ - elseif($this->token[0][0] == "{"){ - - /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */ - - /* the first line is the len field {xx}, which we don't care about at this point */ - $this->line = fgets($this->fp,1024); - while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ - $this->response[]=$this->line; - $this->line = fgets($this->fp, 1024); - } - if(substr($this->line,0,2) == "OK") - return true; - else - return false; - } /* end elseif */ - elseif($this->token[0][0] == "\""){ - - /* I'm going under the _assumption_ that the only function that will get here is the listscripts(). - I could very well be mistaken here, if I am, this part needs some rework */ - - $this->found_script=false; - - while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ - $this->found_script=true; - list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2); - //hmmm, a bug in php, if there is no space on explode line, a warning is generated... - - if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){ - $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1); - } - else - $this->response[] = substr(rtrim($this->ltoken),1,-1); - $this->line = fgets($this->fp, 1024); - } /* end while */ - - return true; - - } /* end elseif */ - else{ - $this->error = EC_UNKNOWN; - $this->error_raw = $this->line; - print "UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line
"; - return false; - } /* end else */ - } /* end get_response() */ - - function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5") - { - $this->host=$host; - $this->port=$port; - $this->user=$user; - $this->pass=$pass; - if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */ - $this->auth = $this->user; - else - $this->auth = $auth; - $this->auth_types=$auth_types; /* Allowed authentication types */ - $this->fp=0; - $this->line=""; - $this->retval=""; - $this->tmpfile=""; - $this->fh=0; - $this->len=0; - $this->capabilities=""; - $this->loggedin=false; - $this->error= ""; - $this->error_raw=""; - } - - function parse_for_quotes($string) - { - /* This function tokenizes a line of input by quote marks and returns them as an array */ - - $start = -1; - $index = 0; - - for($ptr = 0; $ptr < strlen($string); $ptr++){ - if($string[$ptr] == '"' and $string[$ptr] != '\\'){ - if($start == -1){ - $start = $ptr; - } /* end if */ - else{ - $token[$index++] = substr($string, $start + 1, $ptr - $start - 1); - $found = true; - $start = -1; - } /* end else */ - - } /* end if */ - - } /* end for */ - - if(isset($token)) - return $token; - else - return false; - } /* end function */ - - function status($string) - { - //this should probably be replaced by a smarter parser. - - /* Need to remove this and all dependencies from the class */ - - switch (substr($string, 0,2)){ - case "NO": - return F_NO; //there should be some function to extract the error code from this line - //NO ("quota") "You are oly allowed x number of scripts" - break; - case "OK": - return F_OK; - break; - default: - switch ($string[0]){ - case "{": - //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter? - return F_HEAD; - break; - default: - return F_DATA; - break; - } /* end switch */ - } /* end switch */ - } /* end status() */ - - function sieve_login() - { - - $this->fp=fsockopen($this->host,$this->port); - if($this->fp == false) - return false; - - $this->line=fgets($this->fp,1024); - - //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard - //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}" - //So, if we see IMLEMENTATION in the first line, then we are done. - - if(ereg("IMPLEMENTATION",$this->line)) - { - //we're on the Cyrus V2 sieve server - while(sieve::status($this->line) == F_DATA){ - - $this->item = sieve::parse_for_quotes($this->line); - - if(strcmp($this->item[0], "IMPLEMENTATION") == 0) - $this->capabilities["implementation"] = $this->item[1]; - - elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){ - - if(strcmp($this->item[0], "SIEVE") == 0) - $this->cap_type="modules"; - else - $this->cap_type="auth"; - - $this->modules = split(" ", $this->item[1]); - if(is_array($this->modules)){ - foreach($this->modules as $this->module) - $this->capabilities[$this->cap_type][$this->module]=true; - } /* end if */ - elseif(is_string($this->modules)) - $this->capabilites[$this->cap_type][$this->modules]=true; - } - else{ - $this->capabilities["unknown"][]=$this->line; - } - $this->line=fgets($this->fp,1024); - - }// end while - } - else - { - //we're on the older Cyrus V1. server - //this version does not support module reporting. We only have auth types. - $this->cap_type="auth"; - - //break apart at the "Cyrus timsieve...." "SASL={......}" - $this->item = sieve::parse_for_quotes($this->line); - - $this->capabilities["implementation"] = $this->item[0]; - - //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz} - $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1); - - //then split again at the ", " stuff. - $this->modules = split($this->modules, ", "); - - //fill up our $this->modules property - if(is_array($this->modules)){ - foreach($this->modules as $this->module) - $this->capabilities[$this->cap_type][$this->module]=true; - } /* end if */ - elseif(is_string($this->modules)) - $this->capabilites[$this->cap_type][$this->module]=true; - } - - - - - if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes? - $this->error=EC_UNKNOWN; - $this->error_raw = "Server not allowing connections."; - return false; - } - - /* decision login to decide what type of authentication to use... */ - - /* Loop through each allowed authentication type and see if the server allows the type */ - foreach(split(" ",$this->auth_types) as $auth_type) - { - if ($this->capabilities["auth"][$auth_type]) - { - /* We found an auth type that is allowed. */ - $this->auth_in_use = $auth_type; - break; - } - } - - /* Fill error message if no auth types are present */ - if (!isset($this->capabilities["auth"])){ - $this->error=EC_UNKNOWN; - $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules"; - return false; - } - - /* call our authentication program */ - return sieve::authenticate(); - - } - - function sieve_logout() - { - if($this->loggedin==false) - return false; - - fputs($this->fp,"LOGOUT\r\n"); - fclose($this->fp); - $this->loggedin=false; - return true; - } - - function sieve_sendscript($scriptname, $script) - { - if($this->loggedin==false) - return false; - $this->script=stripslashes($script); - $len=strlen($this->script); - fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n"); - fputs($this->fp, "$this->script\r\n"); - - return sieve::get_response(); - - } - - //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src. - //don't expect this function to work yet. I might have messed something up here, too. - function sieve_havespace($scriptname, $scriptsize) - { - if($this->loggedin==false) - return false; - fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n"); - return sieve::get_response(); - - } - - function sieve_setactivescript($scriptname) - { - if($this->loggedin==false) - return false; - - fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n"); - return sieve::get_response(); - - } - - function sieve_getscript($scriptname) - { - unset($this->script); - if($this->loggedin==false) - return false; - - fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n"); - return sieve::get_response(); - - } - - - function sieve_deletescript($scriptname) - { - if($this->loggedin==false) - return false; - - fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n"); - - return sieve::get_response(); - } - - - function sieve_listscripts() - { - fputs($this->fp, "LISTSCRIPTS\r\n"); - sieve::get_response(); //should always return true, even if there are no scripts... - if(isset($this->found_script) and $this->found_script) - return true; - else{ - $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found... - $this->error_raw="No scripts found for this account."; - return false; - } - } - - function sieve_alive() - { - if(!isset($this->fp) or $this->fp==0){ - $this->error = EC_NOT_LOGGED_IN; - return false; - } - elseif(feof($this->fp)){ - $this->error = EC_NOT_LOGGED_IN; - return false; - } - else - return true; - } - - function authenticate() - { - - switch ($this->auth_in_use) { - - case "PLAIN": - $auth=base64_encode("$this->user\0$this->auth\0$this->pass"); - - $this->len=strlen($auth); - fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n"); - fputs($this->fp, "$auth\r\n"); - - $this->line=fgets($this->fp,1024); - while(sieve::status($this->line) == F_DATA) - $this->line=fgets($this->fp,1024); - - if(sieve::status($this->line) == F_NO) - return false; - $this->loggedin=true; - return true; - break; - - default: - return false; - break; - - }//end switch - - - }//end authenticate() - - -} - - - -?> diff --git a/include/sieve/class_parser.inc b/include/sieve/class_parser.inc new file mode 100644 index 000000000..7ca1f9e3b --- /dev/null +++ b/include/sieve/class_parser.inc @@ -0,0 +1,319 @@ +status_text = "incomplete"; + + $this->script_ = $script; + $this->tree_ = new Tree(Scanner::scriptStart()); + $this->tree_->setDumpFunc(array(&$this, 'dumpToken_')); + $this->scanner_ = new Scanner($this->script_); + $this->scanner_->setCommentFunc(array($this, 'comment_')); + + if ($this->commands_($this->tree_->getRoot()) && + $this->scanner_->nextTokenIs('script-end')) + { + return $this->success_('success'); + } + + return $this->status_; + } + + function dumpParseTree() + { + return $this->tree_->dump(); + } + + function dumpToken_(&$token) + { + if (is_array($token)) + { + $str = "<" . $token['text'] . "> "; + foreach ($token as $k => $v) + { + $str .= " $k:$v"; + } + return $str; + } + + return strval($token); + } + + function success_($text = null) + { + if ($text != null) + { + $this->status_text = $text; + } + + return $this->status_ = true; + } + + function error_($text, $token = null) + { + if ($token != null) + { + $text = 'line '. $token['line'] .': '. $token['class'] . " where $text expected near ". $token['text']; + } + + $this->status_text = $text; + return $this->status_ = false; + } + + function done_() + { + $this->status_ = true; + return false; + } + + function comment_($token) + { + $this->tree_->addChild($token); + } + + function commands_($parent_id) + { + while ($this->command_($parent_id)) + ; + + return $this->status_; + } + + function command_($parent_id) + { + if (!$this->scanner_->nextTokenIs('identifier')) + { + if ($this->scanner_->nextTokenIs(array('block-end', 'script-end'))) + { + return $this->done_(); + } + return $this->error_('identifier', $this->scanner_->peekNextToken()); + } + + // Get and check a command token + $token = $this->scanner_->nextToken(); + $semantics = new Semantics($token['text']); + if ($semantics->unknown) + { + return $this->error_('unknown command: '. $token['text']); + } + + $last = $this->tree_->getLastNode($parent_id); + if (!$semantics->validAfter($last['text'])) + { + return $this->error_('"'. $token['text'] .'" may not appear after "'. $last['text'] .'"'); + } + + // Process eventual arguments + $this_node = $this->tree_->addChildTo($parent_id, $token); + if ($this->arguments_($this_node, $semantics) == false) + { + return false; + } + + $token = $this->scanner_->nextToken(); + if ($token['class'] != 'semicolon') + { + if (!$semantics->validToken($token['class'], $token['text'], $token['line'])) + { + return $this->error_($semantics->message); + } + + if ($token['class'] == 'block-start') + { + $this->tree_->addChildTo($this_node, $token); + $ret = $this->block_($this_node, $semantics); + return $ret; + } + + return $this->error_('semicolon', $token); + } + + $this->tree_->addChildTo($this_node, $token); + return $this->success_(); + } + + function arguments_($parent_id, &$semantics) + { + while ($this->argument_($parent_id, &$semantics)) + ; + + if ($this->status_ == true) + { + $this->testlist_($parent_id, $semantics); + } + + return $this->status_; + } + + function argument_($parent_id, &$semantics) + { + if ($this->scanner_->nextTokenIs(array('number', 'tag'))) + { + // Check if semantics allow a number or tag + $token = $this->scanner_->nextToken(); + if (!$semantics->validToken($token['class'], $token['text'], $token['line'])) + { + return $this->error_($semantics->message); + } + + $this->tree_->addChildTo($parent_id, $token); + return $this->success_(); + } + + return $this->stringlist_($parent_id, &$semantics); + } + + function stringlist_($parent_id, &$semantics) + { + if (!$this->scanner_->nextTokenIs('left-bracket')) + { + return $this->string_($parent_id, &$semantics); + } + + $token = $this->scanner_->nextToken(); + if (!$semantics->startStringList($token['line'])) + { + return $this->error_($semantics->message); + } + $this->tree_->addChildTo($parent_id, $token); + + while ($token['class'] != 'right-bracket') + { + if (!$this->string_($parent_id, &$semantics)) + { + return $this->status_; + } + + $token = $this->scanner_->nextToken(); + + if ($token['class'] != 'comma' && $token['class'] != 'right-bracket') + { + return $this->error_('comma or closing bracket', $token); + } + + $this->tree_->addChildTo($parent_id, $token); + } + + $semantics->endStringList(); + return $this->success_(); + } + + function string_($parent_id, &$semantics) + { + if (!$this->scanner_->nextTokenIs(array('quoted-string', 'multi-line'))) + { + return $this->done_(); + } + + $token = $this->scanner_->nextToken(); + if (!$semantics->validToken('string', $token['text'], $token['line'])) + { + return $this->error_($semantics->message); + } + + $this->tree_->addChildTo($parent_id, $token); + return $this->success_(); + } + + function testlist_($parent_id, &$semantics) + { + if (!$this->scanner_->nextTokenIs('left-parant')) + { + return $this->test_($parent_id, $semantics); + } + + $token = $this->scanner_->nextToken(); + if (!$semantics->validToken($token['class'], $token['text'], $token['line'])) + { + return $this->error_($semantics->message); + } + $this->tree_->addChildTo($parent_id, $token); + + while ($token['class'] != 'right-parant') + { + if (!$this->test_($parent_id, $semantics)) + { + return $this->status_; + } + + $token = $this->scanner_->nextToken(); + + if ($token['class'] != 'comma' && $token['class'] != 'right-parant') + { + return $this->error_('comma or closing paranthesis', $token); + } + + $this->tree_->addChildTo($parent_id, $token); + } + + return $this->success_(); + } + + function test_($parent_id, &$semantics) + { + if (!$this->scanner_->nextTokenIs('identifier')) + { + // There is no test + return $this->done_(); + } + + // Check if semantics allow an identifier + $token = $this->scanner_->nextToken(); + if (!$semantics->validToken($token['class'], $token['text'], $token['line'])) + { + return $this->error_($semantics->message); + } + + // Get semantics for this test command + $this_semantics = new Semantics($token['text']); + if ($this_semantics->unknown) + { + return $this->error_('unknown test: '. $token['text']); + } + + $this_node = $this->tree_->addChildTo($parent_id, $token); + + // Consume eventual argument tokens + if (!$this->arguments_($this_node, $this_semantics)) + { + return false; + } + + // Check if arguments were all there + $token = $this->scanner_->peekNextToken(); + if (!$this_semantics->done($token['class'], $token['text'], $token['line'])) + { + return $this->error_($this_semantics->message); + } + + return true; + } + + function block_($parent_id, &$semantics) + { + if ($this->commands_($parent_id, $semantics)) + { + $token = $this->scanner_->nextToken(); + + if ($token['class'] != 'block-end') + { + return $this->error_('closing curly brace', $token); + } + + $this->tree_->addChildTo($parent_id, $token); + return $this->success_(); + } + return $this->status_; + } +} + +?> diff --git a/include/sieve/class_scanner.inc b/include/sieve/class_scanner.inc new file mode 100644 index 000000000..3e22bb1d0 --- /dev/null +++ b/include/sieve/class_scanner.inc @@ -0,0 +1,140 @@ +_construct($script); + } + + function _construct(&$script) + { + if ($script === null) + { + return; + } + + $this->tokenize($script); + } + + function setCommentFunc($callback) + { + if ($callback == null || is_callable($callback)) + { + $this->commentFn_ = $callback; + } + } + + function tokenize(&$script) + { + $pos = 0; + $line = 1; + $script_length = mb_strlen($script); + + while ($pos < $script_length) + { + foreach ($this->token_match_ as $class => $regex) + { + if (preg_match('/^'. $regex .'/', mb_substr($script, $pos), $match)) + { + $length = mb_strlen($match[0]); + + if ($class != 'whitespace') + { + array_push($this->tokens_, array( + 'class' => $class, + 'text' => chop(mb_substr($script, $pos, $length)), + 'line' => $line, + )); + } + if ($class == 'unknown') + { + return; + } + + $pos += $length; + $line += mb_substr_count($match[0], "\n"); + break; + } + } + } + + array_push($this->tokens_, array( + 'class' => 'script-end', + 'text' => 'script-end', + 'line' => $line, + )); + } + + function nextTokenIs($class) + { + $offset = 0; + do + { + $next = $this->tokens_[$this->tokenPos_ + $offset++]['class']; + } + while ($next == 'comment'); + + if (is_array($class)) + { + return in_array($next, $class); + } + else if (is_string($class)) + { + return (strcmp($next, $class) == 0); + } + return false; + } + + function peekNextToken() + { + return $this->tokens_[$this->tokenPos_]; + } + + function nextToken() + { + $token = $this->tokens_[$this->tokenPos_++]; + while ($token['class'] == 'comment') + { + if ($this->commentFn_ != null) + { + call_user_func($this->commentFn_, $token); + } + $token = $this->tokens_[$this->tokenPos_++]; + } + return $token; + } + + function scriptStart() + { + return array( + 'class' => 'script-start', + 'text' => 'script-start', + 'line' => 1, + ); + } + + var $commentFn_ = null; + var $tokenPos_ = 0; + var $tokens_ = array(); + var $token_match_ = array ( + 'left-bracket' => '\[', + 'right-bracket' => '\]', + 'block-start' => '\{', + 'block-end' => '\}', + 'left-parant' => '\(', + 'right-parant' => '\)', + 'comma' => ',', + 'semicolon' => ';', + 'whitespace' => '[ \r\n\t]+', + 'tag' => ':[[:alpha:]_][[:alnum:]_]*(?=\b)', + 'quoted-string' => '"(?:\\[\\"]|[^\x00"])*"', + 'number' => '[[:digit:]]+(?:[KMG])?(?=\b)', + 'comment' => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?\n)', + 'multi-line' => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n', + 'identifier' => '[[:alpha:]_][[:alnum:]_]*(?=\b)', + 'unknown token' => '[^ \r\n\t]+' + ); +} + +?> \ No newline at end of file diff --git a/include/sieve/class_semantics.inc b/include/sieve/class_semantics.inc new file mode 100644 index 000000000..e4b1f4c19 --- /dev/null +++ b/include/sieve/class_semantics.inc @@ -0,0 +1,553 @@ +command_ = $command; + $this->unknown = false; + switch ($command) + { + + /******************** + * control commands + */ + case 'require': + /* require */ + $this->s_ = array( + 'valid_after' => array('script-start', 'require'), + 'arguments' => array( + array('class' => 'string', 'list' => true, 'name' => 'require-string', 'occurrences' => '1', 'call' => 'setRequire_', 'values' => array( + array('occurrences' => '+', 'regex' => '"'. $this->requireStrings_ .'"'), + array('occurrences' => '+', 'regex' => '"comparator-i;(octet|ascii-casemap|ascii-numeric)"') + )) + ) + ); + break; + + case 'if': + /* if */ + $this->s_ = array( + 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else', + 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'), + 'arguments' => array( + array('class' => 'identifier', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test') + )), + array('class' => 'block-start', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '{', 'name' => 'block') + )) + ) + ); + break; + + case 'elsif': + /* elsif */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif'), + 'arguments' => array( + array('class' => 'identifier', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test') + )), + array('class' => 'block-start', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '{', 'name' => 'block') + )) + ) + ); + break; + + case 'else': + /* else */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif'), + 'arguments' => array( + array('class' => 'block-start', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '{', 'name' => 'block') + )) + ) + ); + break; + + + /******************* + * action commands + */ + case 'keep': + case 'stop': + case 'discard': + /* keep / stop / discard */ + $this->s_ = array( + 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else', + 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard') + ); + break; + + case 'fileinto': + /* fileinto */ + $this->s_ = array( + 'requires' => 'fileinto', + 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'), + 'arguments' => array( + array('class' => 'string', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder') + )) + ) + ); + break; + + case 'redirect': + /* redirect */ + $this->s_ = array( + 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'), + 'arguments' => array( + array('class' => 'string', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '".*"', 'name' => 'address') + )) + ) + ); + break; + + case 'reject': + /* reject */ + $this->s_ = array( + 'requires' => 'reject', + 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'), + 'arguments' => array( + array('class' => 'string', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason') + )) + ) + ); + break; + + case 'vacation': + /* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] */ + $this->s_ = array( + 'requires' => 'vacation', + 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'), + 'arguments' => array( + array('class' => 'tag', 'occurrences' => '*', 'values' => array( + array('occurrences' => '?', 'regex' => ':days', 'name' => 'days', + 'add' => array( + array('class' => 'number', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '.*', 'name' => 'period') + )) + ) + ), + array('occurrences' => '?', 'regex' => ':addresses', 'name' => 'addresses', + 'add' => array( + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'address') + )) + ) + ), + array('occurrences' => '?', 'regex' => ':subject', 'name' => 'subject', + 'add' => array( + array('class' => 'string', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '".*"', 'name' => 'subject') + )) + ) + ), + array('occurrences' => '?', 'regex' => ':mime', 'name' => 'mime') + )), + array('class' => 'string', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason') + )) + ) + ); + break; + + + /***************** + * test commands + */ + case 'address': + /* address [address-part: tag] [comparator: tag] [match-type: tag] */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'), + 'arguments' => array( + array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array( + array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'), + array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'), + array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator', + 'add' => array( + array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array( + array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'), + array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string') + )) + ) + ) + )), + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header') + )), + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key') + )) + ) + ); + break; + + case 'allof': + case 'anyof': + /* allof + anyof */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'), + 'arguments' => array( + array('class' => 'left-parant', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list') + )), + array('class' => 'identifier', 'occurrences' => '+', 'values' => array( + array('occurrences' => '+', 'regex' => $this->testCommands_, 'name' => 'test') + )) + ) + ); + break; + + case 'envelope': + /* envelope [address-part: tag] [comparator: tag] [match-type: tag] */ + $this->s_ = array( + 'requires' => 'envelope', + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'), + 'arguments' => array( + array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array( + array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'), + array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'), + array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator', + 'add' => array( + array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array( + array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'), + array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string') + )) + ) + ) + )), + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'envelope-part') + )), + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key') + )) + ) + ); + break; + + case 'exists': + /* exists */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'), + 'arguments' => array( + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header') + )) + ) + ); + break; + + case 'header': + /* header [comparator: tag] [match-type: tag] */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'), + 'arguments' => array( + array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array( + array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'), + array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator', + 'add' => array( + array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array( + array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'), + array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string') + )) + ) + ) + )), + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header') + )), + array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array( + array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key') + )) + ) + ); + break; + + case 'not': + /* not */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'), + 'arguments' => array( + array('class' => 'identifier', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test') + )) + ) + ); + break; + + case 'size': + /* size <":over" / ":under"> */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'), + 'arguments' => array( + array('class' => 'tag', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type') + )), + array('class' => 'number', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '.*', 'name' => 'limit') + )) + ) + ); + break; + + case 'true': + case 'false': + /* true / false */ + $this->s_ = array( + 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not') + ); + break; + + + /******************** + * unknown commands + */ + default: + $this->unknown = true; + } + } + + function setRequire_($text) + { + global $requires_; + array_push($requires_, $text); + return true; + } + + function setMatchType_($text) + { + // Do special processing for relational test extension + if ($text == ':count' || $text == ':value') + { + global $requires_; + if (!in_array('"relational"', $requires_)) + { + $this->message = 'missing require for match-type '. $text; + return false; + } + + array_unshift($this->s_['arguments'], + array('class' => 'string', 'occurrences' => '1', 'values' => array( + array('occurrences' => '1', 'regex' => '"(lt|le|eq|ge|gt|ne)"', 'name' => 'relation-string'), + )) + ); + } + $this->matchType_ = $text; + return true; + } + + function setComparator_($text) + { + $this->comparator_ = $text; + return true; + } + + function checkAddrPart_($text) + { + if ($text == ':user' || $text == ':detail') + { + global $requires_; + if (!in_array('"subaddress"', $requires_)) + { + $this->message = 'missing require for tag '. $text; + return false; + } + } + return true; + } + + function checkTags_() + { + if (isset($this->matchType_) && + $this->matchType_ == ':count' && + $this->comparator_ != '"i;ascii-numeric"') + { + $this->message = 'match-type :count needs comparator i;ascii-numeric'; + return false; + } + return true; + } + + function validAfter($prev) + { + return in_array($prev, $this->s_['valid_after']); + } + + function validClass_($class, $id) + { + // Check if command expects any arguments + if (!isset($this->s_['arguments'])) + { + $this->message = $id .' where semicolon expected'; + return false; + } + + foreach ($this->s_['arguments'] as $arg) + { + if ($class == $arg['class']) + { + return true; + } + + // Is the argument required + if ($arg['occurrences'] != '?' && $arg['occurrences'] != '*') + { + $this->message = $id .' where '. $arg['class'] .' expected'; + return false; + } + + if (isset($arg['post-call']) && + !call_user_func(array(&$this, $arg['post-call']))) + { + return false; + } + array_shift($this->s_['arguments']); + } + + $this->message = 'unexpected '. $id; + return false; + } + + function startStringList($line) + { + if (!$this->validClass_('string', 'string')) + { + $this->message = 'line '. $line .': '. $this->message; + return false; + } + else if (!isset($this->s_['arguments'][0]['list'])) + { + $this->message = 'line '. $line .': '. 'left bracket where '. $this->s_['arguments'][0]['class'] .' expected'; + return false; + } + + $this->s_['arguments'][0]['occurrences'] = '+'; + return true; + } + + function endStringList() + { + array_shift($this->s_['arguments']); + } + + function validToken($class, &$text, &$line) + { + $name = $class . ($class != $text ? " $text" : ''); + + // Check if the command needs to be required + // TODO: move this to somewhere more appropriate + global $requires_; + if (isset($this->s_['requires']) && + !in_array('"'.$this->s_['requires'].'"', $requires_)) + { + $this->message = 'line '. $line .': missing require for '. $this->command_; + return false; + } + + // Make sure the argument has a valid class + if (!$this->validClass_($class, $name)) + { + $this->message = 'line '. $line .': '. $this->message; + return false; + } + + $arg = &$this->s_['arguments'][0]; + foreach ($arg['values'] as $val) + { + if (preg_match('/^'. $val['regex'] .'$/m', $text)) + { + // Check if the argument value needs a 'require' + if (isset($val['requires']) && + !in_array('"'.$val['requires'].'"', $requires_)) + { + $this->message = 'line '. $line .': missing require for '. $val['name'] .' '. $text; + return false; + } + + // Check if a possible value of this argument may occur + if ($val['occurrences'] == '?' || $val['occurrences'] == '1') + { + $val['occurrences'] = '0'; + } + else if ($val['occurrences'] == '+') + { + $val['occurrences'] = '*'; + } + else if ($val['occurrences'] == '0') + { + $this->message = 'line '. $line .': too many '. $val['name'] .' '. $class .'s near '. $text; + return false; + } + + // Call extra processing function if defined + if (isset($val['call']) && !call_user_func(array(&$this, $val['call']), $text) || + isset($arg['call']) && !call_user_func(array(&$this, $arg['call']), $text)) + { + $this->message = 'line '. $line .': '. $this->message; + return false; + } + + // Set occurrences appropriately + if ($arg['occurrences'] == '?' || $arg['occurrences'] == '1') + { + array_shift($this->s_['arguments']); + } + else + { + $arg['occurrences'] = '*'; + } + + // Add argument(s) expected to follow right after this one + if (isset($val['add'])) + { + while ($add_arg = array_pop($val['add'])) + { + array_unshift($this->s_['arguments'], $add_arg); + } + } + + return true; + } + } + + $this->message = 'line '. $line .': unexpected '. $name; + return false; + } + + function done($class, $text, $line) + { + if (isset($this->s_['arguments'])) + { + foreach ($this->s_['arguments'] as $arg) + { + if ($arg['occurrences'] == '+' || $arg['occurrences'] == '1') + { + $this->message = 'line '. $line .': '. $class .' '. $text .' where '. $arg['class'] .' expected'; + return false; + } + } + } + return true; + } +} + +?> \ No newline at end of file diff --git a/include/sieve/class_sieve.inc b/include/sieve/class_sieve.inc new file mode 100644 index 000000000..bdbf7d6fd --- /dev/null +++ b/include/sieve/class_sieve.inc @@ -0,0 +1,514 @@ + + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + */ + +// TODO before next release: remove ::status() and dependencies + + +define ("F_NO", 0); +define ("F_OK", 1); +define ("F_DATA", 2); +define ("F_HEAD", 3); + +define ("EC_NOT_LOGGED_IN", 0); +define ("EC_QUOTA", 10); +define ("EC_NOSCRIPTS", 20); +define ("EC_UNKNOWN", 255); +/* + +SIEVE-PHP.LIB VERSION 0.0.8 + +(C) 2001 Dan Ellis. + +PLEASE READ THE README FILE FOR MORE INFORMATION. + +Basically, this is the first re-release. Things are much better than before. + +Notes: +This program/libary has bugs. + . This was quickly hacked out, so please let me know what is wrong and if you feel ambitious submit + a patch :). + +Todo: + . Provide better error diagnostics. (mostly done with ver 0.0.5) + . Allow other auth mechanisms besides plain (in progress) + . Have timing mechanism when port problems arise. (not done yet) + . Maybe add the NOOP function. (not done yet) + . Other top secret stuff.... (some done, believe me?) + +Dan Ellis (danellis@rushmore.com) + +This program is released under the GNU Public License. + +You should have received a copy of the GNU Public + License along with this package; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + +See CHANGES for updates since last release + +Contributers of patches: + Atif Ghaffar + Andrew Sterling Hanenkamp +*/ + + +class sieve +{ + var $host; + var $port; + var $user; + var $pass; + var $auth_types; /* a comma seperated list of allowed auth types, in order of preference */ + var $auth_in_use; /* type of authentication attempted */ + + var $line; + var $fp; + var $retval; + var $tmpfile; + var $fh; + var $len; + var $script; + + var $loggedin; + var $capabilities; + var $error; + var $error_raw; + var $responses; + + //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN) + //so we can decide how to handle certain errors?!? + + //also add a connection type, like PLAIN, MD5, etc... + + + function get_response() + { + if($this->loggedin == false or feof($this->fp)){ + $this->error = EC_NOT_LOGGED_IN; + $this->error_raw = "You are not logged in."; + return false; + } + + unset($this->response); + unset($this->error); + unset($this->error_raw); + + $this->line=fgets($this->fp,1024); + $this->token = split(" ", $this->line, 2); + + if($this->token[0] == "NO"){ + /* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of: + NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */ + $this->x = 0; + list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3); + if($this->mtoken[0] == "{"){ + while($this->mtoken[$this->x] != "}" or $this->err_len < 1){ + $this->err_len = substr($this->mtoken, 1, $this->x); + $this->x++; + } + //print "
Trying to receive $this->err_len bytes for result
"; + $this->line = fgets($this->fp,$this->err_len); + $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's + $this->err_recv = strlen($this->line); + + while($this->err_recv < $this->err_len){ + //print "
Trying to receive ".($this->err_len-$this->err_recv)." bytes for result
"; + $this->line = fgets($this->fp, ($this->err_len-$this->err_recv)); + $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's + $this->err_recv += strlen($this->line); + } /* end while */ + $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug... + $this->error=EC_UNKNOWN; + + } /* end if */ + elseif($this->mtoken[0] == "("){ + switch($this->mtoken){ + case "(\"QUOTA\")": + $this->error = EC_QUOTA; + $this->error_raw=$this->rtoken; + break; + default: + $this->error = EC_UNKNOWN; + $this->error_raw=$this->rtoken; + break; + } /* end switch */ + } /* end elseif */ + else{ + $this->error = EC_UNKNOWN; + $this->error_raw = $this->line; + } + return false; + + } /* end if */ + elseif(substr($this->token[0],0,-2) == "OK"){ + return true; + } /* end elseif */ + elseif($this->token[0][0] == "{"){ + + /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */ + + /* the first line is the len field {xx}, which we don't care about at this point */ + $this->line = fgets($this->fp,1024); + while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ + $this->response[]=$this->line; + $this->line = fgets($this->fp, 1024); + } + if(substr($this->line,0,2) == "OK") + return true; + else + return false; + } /* end elseif */ + elseif($this->token[0][0] == "\""){ + + /* I'm going under the _assumption_ that the only function that will get here is the listscripts(). + I could very well be mistaken here, if I am, this part needs some rework */ + + $this->found_script=false; + + while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ + $this->found_script=true; + list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2); + //hmmm, a bug in php, if there is no space on explode line, a warning is generated... + + if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){ + $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1); + } + else + $this->response[] = substr(rtrim($this->ltoken),1,-1); + $this->line = fgets($this->fp, 1024); + } /* end while */ + + return true; + + } /* end elseif */ + else{ + $this->error = EC_UNKNOWN; + $this->error_raw = $this->line; + print "UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line
"; + return false; + } /* end else */ + } /* end get_response() */ + + function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5") + { + $this->host=$host; + $this->port=$port; + $this->user=$user; + $this->pass=$pass; + if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */ + $this->auth = $this->user; + else + $this->auth = $auth; + $this->auth_types=$auth_types; /* Allowed authentication types */ + $this->fp=0; + $this->line=""; + $this->retval=""; + $this->tmpfile=""; + $this->fh=0; + $this->len=0; + $this->capabilities=""; + $this->loggedin=false; + $this->error= ""; + $this->error_raw=""; + } + + function parse_for_quotes($string) + { + /* This function tokenizes a line of input by quote marks and returns them as an array */ + + $start = -1; + $index = 0; + + for($ptr = 0; $ptr < strlen($string); $ptr++){ + if($string[$ptr] == '"' and $string[$ptr] != '\\'){ + if($start == -1){ + $start = $ptr; + } /* end if */ + else{ + $token[$index++] = substr($string, $start + 1, $ptr - $start - 1); + $found = true; + $start = -1; + } /* end else */ + + } /* end if */ + + } /* end for */ + + if(isset($token)) + return $token; + else + return false; + } /* end function */ + + function status($string) + { + //this should probably be replaced by a smarter parser. + + /* Need to remove this and all dependencies from the class */ + + switch (substr($string, 0,2)){ + case "NO": + return F_NO; //there should be some function to extract the error code from this line + //NO ("quota") "You are oly allowed x number of scripts" + break; + case "OK": + return F_OK; + break; + default: + switch ($string[0]){ + case "{": + //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter? + return F_HEAD; + break; + default: + return F_DATA; + break; + } /* end switch */ + } /* end switch */ + } /* end status() */ + + function sieve_login() + { + + $this->fp=fsockopen($this->host,$this->port); + if($this->fp == false) + return false; + + $this->line=fgets($this->fp,1024); + + //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard + //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}" + //So, if we see IMLEMENTATION in the first line, then we are done. + + if(ereg("IMPLEMENTATION",$this->line)) + { + //we're on the Cyrus V2 sieve server + while(sieve::status($this->line) == F_DATA){ + + $this->item = sieve::parse_for_quotes($this->line); + + if(strcmp($this->item[0], "IMPLEMENTATION") == 0) + $this->capabilities["implementation"] = $this->item[1]; + + elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){ + + if(strcmp($this->item[0], "SIEVE") == 0) + $this->cap_type="modules"; + else + $this->cap_type="auth"; + + $this->modules = split(" ", $this->item[1]); + if(is_array($this->modules)){ + foreach($this->modules as $this->module) + $this->capabilities[$this->cap_type][$this->module]=true; + } /* end if */ + elseif(is_string($this->modules)) + $this->capabilites[$this->cap_type][$this->modules]=true; + } + else{ + $this->capabilities["unknown"][]=$this->line; + } + $this->line=fgets($this->fp,1024); + + }// end while + } + else + { + //we're on the older Cyrus V1. server + //this version does not support module reporting. We only have auth types. + $this->cap_type="auth"; + + //break apart at the "Cyrus timsieve...." "SASL={......}" + $this->item = sieve::parse_for_quotes($this->line); + + $this->capabilities["implementation"] = $this->item[0]; + + //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz} + $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1); + + //then split again at the ", " stuff. + $this->modules = split($this->modules, ", "); + + //fill up our $this->modules property + if(is_array($this->modules)){ + foreach($this->modules as $this->module) + $this->capabilities[$this->cap_type][$this->module]=true; + } /* end if */ + elseif(is_string($this->modules)) + $this->capabilites[$this->cap_type][$this->module]=true; + } + + + + + if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes? + $this->error=EC_UNKNOWN; + $this->error_raw = "Server not allowing connections."; + return false; + } + + /* decision login to decide what type of authentication to use... */ + + /* Loop through each allowed authentication type and see if the server allows the type */ + foreach(split(" ",$this->auth_types) as $auth_type) + { + if ($this->capabilities["auth"][$auth_type]) + { + /* We found an auth type that is allowed. */ + $this->auth_in_use = $auth_type; + break; + } + } + + /* Fill error message if no auth types are present */ + if (!isset($this->capabilities["auth"])){ + $this->error=EC_UNKNOWN; + $this->error_raw = "No authentication methods found - please check your sieve setup for missing sasl modules"; + return false; + } + + /* call our authentication program */ + return sieve::authenticate(); + + } + + function sieve_logout() + { + if($this->loggedin==false) + return false; + + fputs($this->fp,"LOGOUT\r\n"); + fclose($this->fp); + $this->loggedin=false; + return true; + } + + function sieve_sendscript($scriptname, $script) + { + if($this->loggedin==false) + return false; + $this->script=stripslashes($script); + $len=strlen($this->script); + fputs($this->fp, "PUTSCRIPT \"$scriptname\" {".$len."+}\r\n"); + fputs($this->fp, "$this->script\r\n"); + + return sieve::get_response(); + + } + + //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src. + //don't expect this function to work yet. I might have messed something up here, too. + function sieve_havespace($scriptname, $scriptsize) + { + if($this->loggedin==false) + return false; + fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n"); + return sieve::get_response(); + + } + + function sieve_setactivescript($scriptname) + { + if($this->loggedin==false) + return false; + + fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n"); + return sieve::get_response(); + + } + + function sieve_getscript($scriptname) + { + unset($this->script); + if($this->loggedin==false) + return false; + + fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n"); + return sieve::get_response(); + + } + + + function sieve_deletescript($scriptname) + { + if($this->loggedin==false) + return false; + + fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n"); + + return sieve::get_response(); + } + + + function sieve_listscripts() + { + fputs($this->fp, "LISTSCRIPTS\r\n"); + sieve::get_response(); //should always return true, even if there are no scripts... + if(isset($this->found_script) and $this->found_script) + return true; + else{ + $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found... + $this->error_raw="No scripts found for this account."; + return false; + } + } + + function sieve_alive() + { + if(!isset($this->fp) or $this->fp==0){ + $this->error = EC_NOT_LOGGED_IN; + return false; + } + elseif(feof($this->fp)){ + $this->error = EC_NOT_LOGGED_IN; + return false; + } + else + return true; + } + + function authenticate() + { + + switch ($this->auth_in_use) { + + case "PLAIN": + $auth=base64_encode("$this->user\0$this->auth\0$this->pass"); + + $this->len=strlen($auth); + fputs($this->fp, "AUTHENTICATE \"PLAIN\" {".$this->len."+}\r\n"); + fputs($this->fp, "$auth\r\n"); + + $this->line=fgets($this->fp,1024); + while(sieve::status($this->line) == F_DATA) + $this->line=fgets($this->fp,1024); + + if(sieve::status($this->line) == F_NO) + return false; + $this->loggedin=true; + return true; + break; + + default: + return false; + break; + + }//end switch + + + }//end authenticate() + + +} + + + +?> diff --git a/include/sieve/class_sievescript.php b/include/sieve/class_sievescript.php new file mode 100644 index 000000000..a2e831c80 --- /dev/null +++ b/include/sieve/class_sievescript.php @@ -0,0 +1,321 @@ + + + + + + + + + + + +
+ +
+ <:over|:under:1> + + + + + + +*/ + +class SieveScript +{ + var $_scanner; + var $_script; + var $_tree; + var $_status; + var $_noMoreRequires; + + var $_test_identifier = array('address', 'allof', 'anyof', 'envelope', 'exists', 'false', 'header', 'not', 'size', 'true'); + + var $status_text; + + function parse($script) + { + $this->status_text = "incomplete"; + + $root = 'script_start'; + $this->_noMoreRequires = false; + $this->_script = $script; + $this->_tree = new Tree($root); + $this->_tree->setDumpFunc(array($this, '_dumpToken')); + $this->_scanner = new Scanner($this->_script); + $this->_scanner->setCommentFunc(array($this, '_comment')); + + if ($this->_commands($this->_tree->getRoot()) && + $this->_scanner->nextTokenIs('script-end')) + { + return $this->_success('success'); + } + + return $this->_status; + } + + function _dumpToken(&$token) + { + if (is_array($token)) + { + $str = "<" . chop(mb_substr($this->_script, $token['pos'], $token['len'])) . "> "; + foreach ($token as $k => $v) + { + $str .= " $k:$v"; + } + return $str; + } + + return strval($token); + } + + function _tokenStringIs($token, $text) + { + return mb_substr($this->_script, $token['pos'], $token['len']) == $text; + } + + function _success($text = null) + { + if ($text != null) + { + $this->status_text = $text; + } + + return $this->_status = true; + } + + function _error($text, $token = null) + { + if ($token != null) + { + $text = 'line '. $token['line'] .': '. $token['class'] . " where $text expected near ". + '"'. mb_substr($this->_script, $token['pos'], $token['len']) .'"'; + } + + $this->status_text = $text; + return $this->_status = false; + } + + function _done() + { + return false; + } + + function _comment($token) + { + $this->_tree->addChild($token); + } + + function _commands($parent_id) + { + while ($this->_command($parent_id)); + + return $this->_status; + } + + function _command($parent_id) + { + if (!$this->_scanner->nextTokenIs('identifier')) + { + if ($this->_scanner->nextTokenIs(array('right-curly', 'script-end'))) + { + return $this->_done(); + } + return $this->_error('identifier', $this->_scanner->peekNextToken()); + } + + // Get and check a command token + $token = $this->_scanner->nextToken(); + $command = mb_substr($this->_script, $token['pos'], $token['len']); + + if (!in_array($command, array('if', 'elsif', 'else', 'require', 'stop', 'reject', 'fileinto', 'redirect', 'keep', 'discard'))) + { + return $this->_error('unknown command: '. $command); + } + + if ($command != 'require') + { + $this->_noMoreRequires = true; + } + else if ($this->_noMoreRequires) + { + return $this->_error('misplaced require'); + } + + $this_node = $this->_tree->addChildTo($parent_id, $token); + + if (in_array($command, array('if', 'elsif', 'require', 'reject', 'fileinto', 'redirect'))) + { + // TODO: handle optional arguments in reject + if ($this->_arguments($this_node) == false) + { + return false; + } + } + + $token = $this->_scanner->nextToken(); + if (in_array($command, array('if', 'elsif', 'else'))) + { + if ($token['class'] != 'left-curly') + { + return $this->_error('block', $token); + } + $this->_tree->addChildTo($this_node, $token); + return $this->_block($this_node); + } + else if ($token['class'] != 'semicolon') + { + return $this->_error('semicolon', $token); + } + + $this->_tree->addChildTo($this_node, $token); + return $this->_success(); + } + + function _block($parent_id) + { + //TODO: test if cmd is ok w/ block + if ($this->_commands($parent_id)) + { + $token = $this->_scanner->nextToken(); + + if ($token['class'] != 'right-curly') + { + return $this->_error('closing curly brace', $token); + } + + $this->_tree->addChildTo($parent_id, $token); + return $this->_success(); + } + return $this->_status; + } + + function _arguments($parent_id) + { + while ($this->_argument($parent_id)); + if ($this->_status == true) + { + $this->_testlist($parent_id); + } + return $this->_status; + } + + function _argument($parent_id) + { + if (!$this->_scanner->nextTokenIs(array('number', 'tag'))) + { + return $this->_stringlist($parent_id); + } + else + { + if ($this->_tokenStringIs($this->_tree->getNode($parent_id), 'require')) + { + return $this->_error('stringlist', $this->_scanner->nextToken()); + } + } + + $token = $this->_scanner->nextToken(); + $this->_tree->addChildTo($parent_id, $token); + + return $this->_success(); + } + + function _test($parent_id) + { + if (!$this->_scanner->nextTokenIs('identifier')) + { + return $this->_done(); + } + + $token = $this->_scanner->nextToken(); + $this_node = $this->_tree->addChildTo($parent_id, $token); + + $this->_arguments($this_node); + + //TODO: check test for validity here + + return $this->_status; + } + + function _testlist($parent_id) + { + if (!$this->_scanner->nextTokenIs('left-parant')) + { + return $this->_test($parent_id); + } + + $token = $this->_scanner->nextToken(); + $this->_tree->addChildTo($parent_id, $token); + + while ($token['class'] != 'right-parant') + { + if (!$this->_test($parent_id)) + { + return $this->_status; + } + + $token = $this->_scanner->nextToken(); + + if ($token['class'] != 'comma' && $token['class'] != 'right-parant') + { + return $this->_error('comma or closing paranthesis', $token); + } + + $this->_tree->addChildTo($parent_id, $token); + } + + return $this->_success(); + } + + function _string($parent_id) + { + if (!$this->_scanner->nextTokenIs(array('quoted-string', 'multi-line'))) + { + return $this->_done(); + } + + $this->_tree->addChildTo($parent_id, $this->_scanner->nextToken()); + + return $this->_success(); + } + + function _stringlist($parent_id) + { + if (!$this->_scanner->nextTokenIs('left-bracket')) + { + return $this->_string($parent_id); + } + + $token = $this->_scanner->nextToken(); + $this->_tree->addChildTo($parent_id, $token); + + while ($token['class'] != 'right-bracket') + { + if (!$this->_string($parent_id)) + { + return $this->_status; + } + + $token = $this->_scanner->nextToken(); + + if ($token['class'] != 'comma' && $token['class'] != 'right-bracket') + { + return $this->_error('comma or closing bracket', $token); + } + + $this->_tree->addChildTo($parent_id, $token); + } + + return $this->_success(); + } +} + +?> diff --git a/include/sieve/class_tree.inc b/include/sieve/class_tree.inc new file mode 100644 index 000000000..96bc7a822 --- /dev/null +++ b/include/sieve/class_tree.inc @@ -0,0 +1,175 @@ +_construct($root); + } + + function _construct(&$root) + { + $this->childs_ = array(); + $this->parents_ = array(); + $this->nodes_ = array(); + $this->maxId_ = 0; + + $this->parents_[0] = null; + $this->nodes_[0] = $root; + } + + function addChild(&$child) + { + return $this->addChildTo($this->maxId_, $child); + } + + function addChildTo($parent_id, &$child) + { + if (!is_int($parent_id) || + !isset($this->nodes_[$parent_id])) + { + return null; + } + + if (!isset($this->childs_[$parent_id])) + { + $this->childs_[$parent_id] = array(); + } + + $child_id = ++$this->maxId_; + $this->nodes_[$child_id] = $child; + $this->parents_[$child_id] = $parent_id; + array_push($this->childs_[$parent_id], $child_id); + + return $child_id; + } + + function getRoot() + { + if (!isset($this->nodes_[0])) + { + return null; + } + + return 0; + } + + function getParent($node_id) + { + if (!is_int($node_id) || + !isset($this->nodes_[$node_id])) + { + return null; + } + + return $this->parents_[$node_id]; + } + + function getChilds($node_id) + { + if (!is_int($node_id) || + !isset($this->nodes_[$node_id])) + { + return null; + } + + if (!isset($this->childs_[$node_id])) + { + return array(); + } + + return $this->childs_[$node_id]; + } + + function getNode($node_id) + { + if (!is_int($node_id) || + !isset($this->nodes_[$node_id])) + { + return null; + } + + return $this->nodes_[$node_id]; + } + + function getLastNode($parent_id) + { + $childs = $this->getChilds($parent_id); + + for ($i=count($childs); $i>0; --$i) + { + $node = $this->getNode($childs[$i-1]); + if ($node['text'] == '{') + { + // return command owning the block + return $this->getNode($parent_id); + } + if ($node['class'] != 'comment') + { + return $node; + } + } + + return $this->getNode($parent_id); + } + + function setDumpFunc($callback) + { + if ($callback == NULL || is_callable($callback)) + { + $this->dumpFn_ = $callback; + } + } + + function dump() + { + $this->dump_ = "tree\n"; + $this->doDump_(0, '', true); + return $this->dump_; + } + + function doDump_($node_id, $prefix, $last) + { + if ($last) + { + $infix = '`--- '; + $c_prefix = $prefix . ' '; + } + else + { + $infix = '|--- '; + $c_prefix = $prefix . '| '; + } + + $node = $this->nodes_[$node_id]; + if ($this->dumpFn_ != NULL) + { + $this->dump_ .= $prefix . $infix . call_user_func($this->dumpFn_, $node) . "\n"; + } + else + { + $this->dump_ .= "$prefix$infix$node\n"; + } + + $childs = $this->childs_[$node_id]; + for ($i=0; $idoDump_($childs[$i], $c_prefix, $c_last); + } + } +} + +?> \ No newline at end of file diff --git a/include/sieve/libsieve.php b/include/sieve/libsieve.php new file mode 100644 index 000000000..c438a9ce2 --- /dev/null +++ b/include/sieve/libsieve.php @@ -0,0 +1,7 @@ + diff --git a/plugins/personal/mail/class_mailAccount.inc b/plugins/personal/mail/class_mailAccount.inc index 468e4ab7e..9dcd06822 100644 --- a/plugins/personal/mail/class_mailAccount.inc +++ b/plugins/personal/mail/class_mailAccount.inc @@ -10,7 +10,7 @@ */ /* Load sieve support */ -require_once ("class_sieve.inc"); +require_once ("sieve/libsieve.php"); /* Load mail methods */ global $BASE_DIR;