[30] | 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 | */ |
---|
| 28 | define ("F_NO", 0); |
---|
| 29 | define ("F_OK", 1); |
---|
| 30 | define ("F_DATA", 2); |
---|
| 31 | define ("F_HEAD", 3); |
---|
| 32 | |
---|
| 33 | define ("EC_NOT_LOGGED_IN", 0); |
---|
| 34 | define ("EC_QUOTA", 10); |
---|
| 35 | define ("EC_NOSCRIPTS", 20); |
---|
| 36 | define ("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 | */ |
---|
| 76 | class 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); |
---|
[5593] | 123 | $this->token = preg_split('/ /', $this->line, 2); |
---|
[30] | 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; |
---|
[5593] | 129 | list($this->ltoken, $this->mtoken, $this->rtoken) = preg_split('/ /', $this->line." ", 3); |
---|
[30] | 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 | |
---|
[7655] | 287 | for($ptr = 0; $ptr < strlen($string); ++$ptr){ |
---|
[30] | 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 | |
---|
[5593] | 374 | if(preg_match('/IMPLEMENTATION/',$this->line)) |
---|
[30] | 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 | |
---|
[5593] | 391 | $this->modules = preg_split('/ /', $this->item[1]); |
---|
[30] | 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. |
---|
[5593] | 424 | $this->modules = preg_split("/$this->modules/", ', '); |
---|
[30] | 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": |
---|
[506] | 634 | $auth=base64_encode("$this->user\0$this->auth\0$this->pass"); |
---|
[30] | 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 | |
---|
[5593] | 769 | if(preg_match('/IMPLEMENTATION/',$this->line)) |
---|
[30] | 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 | |
---|
[5593] | 786 | $this->modules = preg_split('/ /', $this->item[1]); |
---|
[30] | 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. |
---|
[5593] | 816 | $this->modules = preg_split("/$this->modules/", ', '); |
---|
[30] | 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 | |
---|
| 838 | if(!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 | */ |
---|
| 858 | function 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 | */ |
---|
| 892 | function 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 | ?> |
---|