1<? 2 3/* 4 * $Id: class.sieve.inc.php 11254 2002-10-16 17:18:24Z lkneschke $ 5 * 6 * Copyright 2001 Dan Ellis <danellis@rushmore.com> 7 * 8 * See the enclosed file COPYING for license information (GPL). If you 9 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. 10 */ 11 12// TODO before next release: remove ::status() and dependencies 13 14 15define ("F_NO", 0); 16define ("F_OK", 1); 17define ("F_DATA", 2); 18define ("F_HEAD", 3); 19 20define ("EC_NOT_LOGGED_IN", 0); 21define ("EC_QUOTA", 10); 22define ("EC_NOSCRIPTS", 20); 23define ("EC_UNKNOWN", 255); 24/* 25 26SIEVE-PHP.LIB VERSION 0.0.8 27 28(C) 2001 Dan Ellis. 29 30PLEASE READ THE README FILE FOR MORE INFORMATION. 31 32Basically, this is the first re-release. Things are much better than before. 33 34Notes: 35This program/libary has bugs. 36 . This was quickly hacked out, so please let me know what is wrong and if you feel ambitious submit 37 a patch :). 38 39Todo: 40 . Provide better error diagnostics. (mostly done with ver 0.0.5) 41 . Allow other auth mechanisms besides plain (in progress) 42 . Have timing mechanism when port problems arise. (not done yet) 43 . Maybe add the NOOP function. (not done yet) 44 . Other top secret stuff.... (some done, believe me?) 45 46Dan Ellis (danellis@rushmore.com) 47 48This program is released under the GNU Public License. 49 50You should have received a copy of the GNU Public 51 License along with this package; if not, write to the 52 Free Software Foundation, Inc., 59 Temple Place - Suite 330, 53 Boston, MA 02111-1307, USA. 54 55See CHANGES for updates since last release 56 57Contributers of patches: 58 Atif Ghaffar 59 Andrew Sterling Hanenkamp <sterling@hanenkamp.com> 60*/ 61 62 63class sieve 64{ 65 var $host; 66 var $port; 67 var $user; 68 var $pass; 69 var $auth_types; /* a comma seperated list of allowed auth types, in order of preference */ 70 var $auth_in_use; /* type of authentication attempted */ 71 72 var $line; 73 var $fp; 74 var $retval; 75 var $tmpfile; 76 var $fh; 77 var $len; 78 var $script; 79 80 var $loggedin; 81 var $capabilities; 82 var $error; 83 var $error_raw; 84 var $responses; 85 86 //maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN) 87 //so we can decide how to handle certain errors?!? 88 89 //also add a connection type, like PLAIN, MD5, etc... 90 91 92 function get_response() 93 { 94 if($this->loggedin == false or feof($this->fp)){ 95 $this->error = EC_NOT_LOGGED_IN; 96 $this->error_raw = "You are not logged in."; 97 return false; 98 } 99 100 unset($this->response); 101 unset($this->error); 102 unset($this->error_raw); 103 104 $this->line=fgets($this->fp,1024); 105 $this->token = split(" ", $this->line, 2); 106 107 if($this->token[0] == "NO"){ 108 /* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of: 109 NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */ 110 $this->x = 0; 111 list($this->ltoken, $this->mtoken, $this->rtoken) = split(" ", $this->line." ", 3); 112 if($this->mtoken[0] == "{"){ 113 while($this->mtoken[$this->x] != "}" or $this->err_len < 1){ 114 $this->err_len = substr($this->mtoken, 1, $this->x); 115 $this->x++; 116 } 117 //print "<br>Trying to receive $this->err_len bytes for result<br>"; 118 $this->line = fgets($this->fp,$this->err_len); 119 $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's 120 $this->err_recv = strlen($this->line); 121 122 while($this->err_recv < $this->err_len){ 123 //print "<br>Trying to receive ".($this->err_len-$this->err_recv)." bytes for result<br>"; 124 $this->line = fgets($this->fp, ($this->err_len-$this->err_recv)); 125 $this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's 126 $this->err_recv += strlen($this->line); 127 } /* end while */ 128 $this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug... 129 $this->error=EC_UNKNOWN; 130 131 } /* end if */ 132 elseif($this->mtoken[0] == "("){ 133 switch($this->mtoken){ 134 case "(\"QUOTA\")": 135 $this->error = EC_QUOTA; 136 $this->error_raw=$this->rtoken; 137 break; 138 default: 139 $this->error = EC_UNKNOWN; 140 $this->error_raw=$this->rtoken; 141 break; 142 } /* end switch */ 143 } /* end elseif */ 144 else{ 145 $this->error = EC_UNKNOWN; 146 $this->error_raw = $this->line; 147 } 148 return false; 149 150 } /* end if */ 151 elseif(substr($this->token[0],0,-2) == "OK"){ 152 return true; 153 } /* end elseif */ 154 elseif($this->token[0][0] == "{"){ 155 156 /* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */ 157 158 /* the first line is the len field {xx}, which we don't care about at this point */ 159 $this->line = fgets($this->fp,1024); 160 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ 161 $this->response[]=$this->line; 162 $this->line = fgets($this->fp, 1024); 163 } 164 if(substr($this->line,0,2) == "OK") 165 return true; 166 else 167 return false; 168 } /* end elseif */ 169 elseif($this->token[0][0] == "\""){ 170 171 /* I'm going under the _assumption_ that the only function that will get here is the listscripts(). 172 I could very well be mistaken here, if I am, this part needs some rework */ 173 174 $this->found_script=false; 175 176 while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){ 177 $this->found_script=true; 178 list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2); 179 //hmmm, a bug in php, if there is no space on explode line, a warning is generated... 180 181 if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){ 182 $this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1); 183 } 184 else 185 $this->response[] = substr(rtrim($this->ltoken),1,-1); 186 $this->line = fgets($this->fp, 1024); 187 } /* end while */ 188 189 return true; 190 191 } /* end elseif */ 192 else{ 193 $this->error = EC_UNKNOWN; 194 $this->error_raw = $this->line; 195 print "<b><i>UNKNOWN ERROR (Please report this line to danellis@rushmore.com to include in future releases): $this->line</i></b><br>"; 196 return false; 197 } /* end else */ 198 } /* end get_response() */ 199 200 function sieve($host, $port, $user, $pass, $auth="", $auth_types="PLAIN DIGEST-MD5") 201 { 202 $this->host=$host; 203 $this->port=$port; 204 $this->user=$user; 205 $this->pass=$pass; 206 if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */ 207 $this->auth = $this->user; 208 else 209 $this->auth = $auth; 210 $this->auth_types=$auth_types; /* Allowed authentication types */ 211 $this->fp=0; 212 $this->line=""; 213 $this->retval=""; 214 $this->tmpfile=""; 215 $this->fh=0; 216 $this->len=0; 217 $this->capabilities=""; 218 $this->loggedin=false; 219 $this->error= ""; 220 $this->error_raw=""; 221 } 222 223 function parse_for_quotes($string) 224 { 225 /* This function tokenizes a line of input by quote marks and returns them as an array */ 226 227 $start = -1; 228 $index = 0; 229 230 for($ptr = 0; $ptr < strlen($string); $ptr++){ 231 if($string[$ptr] == '"' and $string[$ptr] != '\\'){ 232 if($start == -1){ 233 $start = $ptr; 234 } /* end if */ 235 else{ 236 $token[$index++] = substr($string, $start + 1, $ptr - $start - 1); 237 $found = true; 238 $start = -1; 239 } /* end else */ 240 241 } /* end if */ 242 243 } /* end for */ 244 245 if(isset($token)) 246 return $token; 247 else 248 return false; 249 } /* end function */ 250 251 function status($string) 252 { 253 //this should probably be replaced by a smarter parser. 254 255 /* Need to remove this and all dependencies from the class */ 256 257 switch (substr($string, 0,2)){ 258 case "NO": 259 return F_NO; //there should be some function to extract the error code from this line 260 //NO ("quota") "You are oly allowed x number of scripts" 261 break; 262 case "OK": 263 return F_OK; 264 break; 265 default: 266 switch ($string[0]){ 267 case "{": 268 //do parse here for {}'s maybe modify parse_for_quotes to handle any parse delimiter? 269 return F_HEAD; 270 break; 271 default: 272 return F_DATA; 273 break; 274 } /* end switch */ 275 } /* end switch */ 276 } /* end status() */ 277 278 function sieve_login() 279 { 280 $this->fp=fsockopen($this->host,$this->port); 281 if($this->fp == false) 282 return false; 283 284 285 $this->line=fgets($this->fp,1024); 286 //Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard 287 //response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}" 288 //So, if we see IMLEMENTATION in the first line, then we are done. 289 290 if(ereg("IMPLEMENTATION",$this->line)) 291 { 292 //we're on the Cyrus V2 sieve server 293 while(sieve::status($this->line) == F_DATA){ 294 295 $this->item = sieve::parse_for_quotes($this->line); 296 297 if(strcmp($this->item[0], "IMPLEMENTATION") == 0) 298 $this->capabilities["implementation"] = $this->item[1]; 299 300 elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){ 301 302 if(strcmp($this->item[0], "SIEVE") == 0) 303 $this->cap_type="modules"; 304 else 305 $this->cap_type="auth"; 306 307 $this->modules = split(" ", $this->item[1]); 308 if(is_array($this->modules)){ 309 foreach($this->modules as $this->module) 310 $this->capabilities[$this->cap_type][$this->module]=true; 311 } /* end if */ 312 elseif(is_string($this->modules)) 313 $this->capabilites[$this->cap_type][$this->modules]=true; 314 } 315 else{ 316 $this->capabilities["unknown"][]=$this->line; 317 } 318 $this->line=fgets($this->fp,1024); 319 320 }// end while 321 } 322 else 323 { 324 //we're on the older Cyrus V1. server 325 //this version does not support module reporting. We only have auth types. 326 $this->cap_type="auth"; 327 328 //break apart at the "Cyrus timsieve...." "SASL={......}" 329 $this->item = sieve::parse_for_quotes($this->line); 330 331 $this->capabilities["implementation"] = $this->item[0]; 332 333 //we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz} 334 $this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1); 335 336 //then split again at the ", " stuff. 337 $this->modules = split($this->modules, ", "); 338 339 //fill up our $this->modules property 340 if(is_array($this->modules)){ 341 foreach($this->modules as $this->module) 342 $this->capabilities[$this->cap_type][$this->module]=true; 343 } /* end if */ 344 elseif(is_string($this->modules)) 345 $this->capabilites[$this->cap_type][$this->module]=true; 346 } 347 348 349 350 351 if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes? 352 $this->error=EC_UNKNOWN; 353 $this->error_raw = "Server not allowing connections."; 354 return false; 355 } 356 357 /* decision login to decide what type of authentication to use... */ 358 359 /* Loop through each allowed authentication type and see if the server allows the type */ 360 foreach(split(" ",$this->auth_types) as $auth_type) 361 { 362 if ($this->capabilities["auth"][$auth_type]) 363 { 364 /* We found an auth type that is allowed. */ 365 $this->auth_in_use = $auth_type; 366 break; 367 } 368 } 369 370 /* call our authentication program */ 371 372 return sieve::authenticate(); 373 374 } 375 376 function sieve_logout() 377 { 378 if($this->loggedin==false) 379 return false; 380 381 fputs($this->fp,"LOGOUT\r\n"); 382 fclose($this->fp); 383 $this->loggedin=false; 384 return true; 385 } 386 387 function sieve_sendscript($scriptname, $script) 388 { 389 if($this->loggedin==false) 390 return false; 391 $this->script=stripslashes($script); 392 $len=strlen($this->script); 393 fputs($this->fp, "PUTSCRIPT \"$scriptname\" \{$len+}\r\n"); 394 fputs($this->fp, "$this->script\r\n"); 395 396 return sieve::get_response(); 397 398 } 399 400 //it appears the timsieved does not honor the NUMBER type. see lex.c in timsieved src. 401 //don't expect this function to work yet. I might have messed something up here, too. 402 function sieve_havespace($scriptname, $scriptsize) 403 { 404 if($this->loggedin==false) 405 return false; 406 fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n"); 407 return sieve::get_response(); 408 409 } 410 411 function sieve_setactivescript($scriptname) 412 { 413 if($this->loggedin==false) 414 return false; 415 416 fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n"); 417 return sieve::get_response(); 418 419 } 420 421 function sieve_getscript($scriptname) 422 { 423 unset($this->script); 424 if($this->loggedin==false) 425 return false; 426 427 fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n"); 428 return sieve::get_response(); 429 430 } 431 432 433 function sieve_deletescript($scriptname) 434 { 435 if($this->loggedin==false) 436 return false; 437 438 fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n"); 439 440 return sieve::get_response(); 441 } 442 443 444 function sieve_listscripts() 445 { 446 fputs($this->fp, "LISTSCRIPTS\r\n"); 447 sieve::get_response(); //should always return true, even if there are no scripts... 448 if(isset($this->found_script) and $this->found_script) 449 return true; 450 else{ 451 $this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found... 452 $this->error_raw="No scripts found for this account."; 453 return false; 454 } 455 } 456 457 function sieve_alive() 458 { 459 if(!isset($this->fp) or $this->fp==0){ 460 $this->error = EC_NOT_LOGGED_IN; 461 return false; 462 } 463 elseif(feof($this->fp)){ 464 $this->error = EC_NOT_LOGGED_IN; 465 return false; 466 } 467 else 468 return true; 469 } 470 471 function authenticate() 472 { 473 474 switch ($this->auth_in_use) { 475 476 case "PLAIN": 477 $auth=base64_encode("$this->user\0$this->auth\0$this->pass"); 478 479 $this->len=strlen($auth); 480 fputs($this->fp, "AUTHENTICATE \"PLAIN\" \{$this->len+}\r\n"); 481 fputs($this->fp, "$auth\r\n"); 482 483 $this->line=fgets($this->fp,1024); 484 while(sieve::status($this->line) == F_DATA) 485 $this->line=fgets($this->fp,1024); 486 487 if(sieve::status($this->line) == F_NO) 488 return false; 489 $this->loggedin=true; 490 return true; 491 break; 492 493 default: 494 return false; 495 break; 496 497 }//end switch 498 499 500 }//end authenticate() 501 502 503} 504 505 506 507?> 508