PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/listAdmin/inc/sieve-php.lib.php

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