Sieve.php 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334
  1. <?php
  2. /**
  3. * This file contains the Net_Sieve class.
  4. *
  5. * PHP version 5
  6. *
  7. * +-----------------------------------------------------------------------+
  8. * | All rights reserved. |
  9. * | |
  10. * | Redistribution and use in source and binary forms, with or without |
  11. * | modification, are permitted provided that the following conditions |
  12. * | are met: |
  13. * | |
  14. * | o Redistributions of source code must retain the above copyright |
  15. * | notice, this list of conditions and the following disclaimer. |
  16. * | o Redistributions in binary form must reproduce the above copyright |
  17. * | notice, this list of conditions and the following disclaimer in the |
  18. * | documentation and/or other materials provided with the distribution.|
  19. * | |
  20. * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  21. * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  22. * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  23. * | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
  24. * | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  25. * | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
  26. * | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  27. * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  28. * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
  29. * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  30. * | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
  31. * +-----------------------------------------------------------------------+
  32. *
  33. * @category Networking
  34. * @package Net_Sieve
  35. * @author Richard Heyes <richard@phpguru.org>
  36. * @author Damian Fernandez Sosa <damlists@cnba.uba.ar>
  37. * @author Anish Mistry <amistry@am-productions.biz>
  38. * @author Jan Schneider <jan@horde.org>
  39. * @copyright 2002-2003 Richard Heyes
  40. * @copyright 2006-2008 Anish Mistry
  41. * @license http://www.opensource.org/licenses/bsd-license.php BSD
  42. * @link http://pear.php.net/package/Net_Sieve
  43. */
  44. require_once 'PEAR.php';
  45. require_once 'Net/Socket.php';
  46. /**
  47. * TODO
  48. *
  49. * o supportsAuthMech()
  50. */
  51. /**
  52. * Disconnected state
  53. * @const NET_SIEVE_STATE_DISCONNECTED
  54. */
  55. define('NET_SIEVE_STATE_DISCONNECTED', 1, true);
  56. /**
  57. * Authorisation state
  58. * @const NET_SIEVE_STATE_AUTHORISATION
  59. */
  60. define('NET_SIEVE_STATE_AUTHORISATION', 2, true);
  61. /**
  62. * Transaction state
  63. * @const NET_SIEVE_STATE_TRANSACTION
  64. */
  65. define('NET_SIEVE_STATE_TRANSACTION', 3, true);
  66. /**
  67. * A class for talking to the timsieved server which comes with Cyrus IMAP.
  68. *
  69. * @category Networking
  70. * @package Net_Sieve
  71. * @author Richard Heyes <richard@phpguru.org>
  72. * @author Damian Fernandez Sosa <damlists@cnba.uba.ar>
  73. * @author Anish Mistry <amistry@am-productions.biz>
  74. * @author Jan Schneider <jan@horde.org>
  75. * @copyright 2002-2003 Richard Heyes
  76. * @copyright 2006-2008 Anish Mistry
  77. * @license http://www.opensource.org/licenses/bsd-license.php BSD
  78. * @version Release: @package_version@
  79. * @link http://pear.php.net/package/Net_Sieve
  80. * @link http://tools.ietf.org/html/rfc5228 RFC 5228 (Sieve: An Email
  81. * Filtering Language)
  82. * @link http://tools.ietf.org/html/rfc5804 RFC 5804 A Protocol for
  83. * Remotely Managing Sieve Scripts
  84. */
  85. class Net_Sieve
  86. {
  87. /**
  88. * The authentication methods this class supports.
  89. *
  90. * Can be overwritten if having problems with certain methods.
  91. *
  92. * @var array
  93. */
  94. var $supportedAuthMethods = array(
  95. 'DIGEST-MD5',
  96. 'CRAM-MD5',
  97. 'EXTERNAL',
  98. 'PLAIN' ,
  99. 'LOGIN'
  100. );
  101. /**
  102. * SASL authentication methods that require Auth_SASL.
  103. *
  104. * @var array
  105. */
  106. var $supportedSASLAuthMethods = array('DIGEST-MD5', 'CRAM-MD5');
  107. /**
  108. * The socket handle.
  109. *
  110. * @var resource
  111. */
  112. var $_sock;
  113. /**
  114. * Parameters and connection information.
  115. *
  116. * @var array
  117. */
  118. var $_data;
  119. /**
  120. * Current state of the connection.
  121. *
  122. * One of the NET_SIEVE_STATE_* constants.
  123. *
  124. * @var integer
  125. */
  126. var $_state;
  127. /**
  128. * PEAR object to avoid strict warnings.
  129. *
  130. * @var PEAR_Error
  131. */
  132. var $_pear;
  133. /**
  134. * Constructor error.
  135. *
  136. * @var PEAR_Error
  137. */
  138. var $_error;
  139. /**
  140. * Whether to enable debugging.
  141. *
  142. * @var boolean
  143. */
  144. var $_debug = false;
  145. /**
  146. * Debug output handler.
  147. *
  148. * This has to be a valid callback.
  149. *
  150. * @var string|array
  151. */
  152. var $_debug_handler = null;
  153. /**
  154. * Whether to pick up an already established connection.
  155. *
  156. * @var boolean
  157. */
  158. var $_bypassAuth = false;
  159. /**
  160. * Whether to use TLS if available.
  161. *
  162. * @var boolean
  163. */
  164. var $_useTLS = true;
  165. /**
  166. * Additional options for stream_context_create().
  167. *
  168. * @var array
  169. */
  170. var $_options = null;
  171. /**
  172. * Maximum number of referral loops
  173. *
  174. * @var array
  175. */
  176. var $_maxReferralCount = 15;
  177. /**
  178. * Constructor.
  179. *
  180. * Sets up the object, connects to the server and logs in. Stores any
  181. * generated error in $this->_error, which can be retrieved using the
  182. * getError() method.
  183. *
  184. * @param string $user Login username.
  185. * @param string $pass Login password.
  186. * @param string $host Hostname of server.
  187. * @param string $port Port of server.
  188. * @param string $logintype Type of login to perform (see
  189. * $supportedAuthMethods).
  190. * @param string $euser Effective user. If authenticating as an
  191. * administrator, login as this user.
  192. * @param boolean $debug Whether to enable debugging (@see setDebug()).
  193. * @param string $bypassAuth Skip the authentication phase. Useful if the
  194. * socket is already open.
  195. * @param boolean $useTLS Use TLS if available.
  196. * @param array $options Additional options for
  197. * stream_context_create().
  198. * @param mixed $handler A callback handler for the debug output.
  199. */
  200. function __construct($user = null, $pass = null, $host = 'localhost',
  201. $port = 2000, $logintype = '', $euser = '',
  202. $debug = false, $bypassAuth = false, $useTLS = true,
  203. $options = null, $handler = null
  204. ) {
  205. $this->_pear = new PEAR();
  206. $this->_state = NET_SIEVE_STATE_DISCONNECTED;
  207. $this->_data['user'] = $user;
  208. $this->_data['pass'] = $pass;
  209. $this->_data['host'] = $host;
  210. $this->_data['port'] = $port;
  211. $this->_data['logintype'] = $logintype;
  212. $this->_data['euser'] = $euser;
  213. $this->_sock = new Net_Socket();
  214. $this->_bypassAuth = $bypassAuth;
  215. $this->_useTLS = $useTLS;
  216. $this->_options = (array) $options;
  217. $this->setDebug($debug, $handler);
  218. /* Try to include the Auth_SASL package. If the package is not
  219. * available, we disable the authentication methods that depend upon
  220. * it. */
  221. if ((@include_once 'Auth/SASL.php') === false) {
  222. $this->_debug('Auth_SASL not present');
  223. $this->supportedAuthMethods = array_diff(
  224. $this->supportedAuthMethods,
  225. $this->supportedSASLAuthMethods
  226. );
  227. }
  228. if (strlen($user) && strlen($pass)) {
  229. $this->_error = $this->_handleConnectAndLogin();
  230. }
  231. }
  232. /**
  233. * Returns any error that may have been generated in the constructor.
  234. *
  235. * @return boolean|PEAR_Error False if no error, PEAR_Error otherwise.
  236. */
  237. function getError()
  238. {
  239. return is_a($this->_error, 'PEAR_Error') ? $this->_error : false;
  240. }
  241. /**
  242. * Sets the debug state and handler function.
  243. *
  244. * @param boolean $debug Whether to enable debugging.
  245. * @param string $handler A custom debug handler. Must be a valid callback.
  246. *
  247. * @return void
  248. */
  249. function setDebug($debug = true, $handler = null)
  250. {
  251. $this->_debug = $debug;
  252. $this->_debug_handler = $handler;
  253. }
  254. /**
  255. * Connects to the server and logs in.
  256. *
  257. * @return boolean True on success, PEAR_Error on failure.
  258. */
  259. function _handleConnectAndLogin()
  260. {
  261. $res = $this->connect($this->_data['host'], $this->_data['port'], $this->_options, $this->_useTLS);
  262. if (is_a($res, 'PEAR_Error')) {
  263. return $res;
  264. }
  265. if ($this->_bypassAuth === false) {
  266. $res = $this->login($this->_data['user'], $this->_data['pass'], $this->_data['logintype'], $this->_data['euser'], $this->_bypassAuth);
  267. if (is_a($res, 'PEAR_Error')) {
  268. return $res;
  269. }
  270. }
  271. return true;
  272. }
  273. /**
  274. * Handles connecting to the server and checks the response validity.
  275. *
  276. * @param string $host Hostname of server.
  277. * @param string $port Port of server.
  278. * @param array $options List of options to pass to
  279. * stream_context_create().
  280. * @param boolean $useTLS Use TLS if available.
  281. *
  282. * @return boolean True on success, PEAR_Error otherwise.
  283. */
  284. function connect($host, $port, $options = null, $useTLS = true)
  285. {
  286. $this->_data['host'] = $host;
  287. $this->_data['port'] = $port;
  288. $this->_useTLS = $useTLS;
  289. if (is_array($options)) {
  290. $this->_options = array_merge($this->_options, $options);
  291. }
  292. if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
  293. return $this->_pear->raiseError('Not currently in DISCONNECTED state', 1);
  294. }
  295. $res = $this->_sock->connect($host, $port, false, 5, $options);
  296. if (is_a($res, 'PEAR_Error')) {
  297. return $res;
  298. }
  299. if ($this->_bypassAuth) {
  300. $this->_state = NET_SIEVE_STATE_TRANSACTION;
  301. } else {
  302. $this->_state = NET_SIEVE_STATE_AUTHORISATION;
  303. $res = $this->_doCmd();
  304. if (is_a($res, 'PEAR_Error')) {
  305. return $res;
  306. }
  307. }
  308. // Explicitly ask for the capabilities in case the connection is
  309. // picked up from an existing connection.
  310. $res = $this->_cmdCapability();
  311. if (is_a($res, 'PEAR_Error')) {
  312. return $this->_pear->raiseError(
  313. 'Failed to connect, server said: ' . $res->getMessage(), 2
  314. );
  315. }
  316. // Check if we can enable TLS via STARTTLS.
  317. if ($useTLS && !empty($this->_capability['starttls'])
  318. && function_exists('stream_socket_enable_crypto')
  319. ) {
  320. $res = $this->_startTLS();
  321. if (is_a($res, 'PEAR_Error')) {
  322. return $res;
  323. }
  324. }
  325. return true;
  326. }
  327. /**
  328. * Disconnect from the Sieve server.
  329. *
  330. * @param boolean $sendLogoutCMD Whether to send LOGOUT command before
  331. * disconnecting.
  332. *
  333. * @return boolean True on success, PEAR_Error otherwise.
  334. */
  335. function disconnect($sendLogoutCMD = true)
  336. {
  337. return $this->_cmdLogout($sendLogoutCMD);
  338. }
  339. /**
  340. * Logs into server.
  341. *
  342. * @param string $user Login username.
  343. * @param string $pass Login password.
  344. * @param string $logintype Type of login method to use.
  345. * @param string $euser Effective UID (perform on behalf of $euser).
  346. * @param boolean $bypassAuth Do not perform authentication.
  347. *
  348. * @return boolean True on success, PEAR_Error otherwise.
  349. */
  350. function login($user, $pass, $logintype = null, $euser = '', $bypassAuth = false)
  351. {
  352. $this->_data['user'] = $user;
  353. $this->_data['pass'] = $pass;
  354. $this->_data['logintype'] = $logintype;
  355. $this->_data['euser'] = $euser;
  356. $this->_bypassAuth = $bypassAuth;
  357. if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) {
  358. return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
  359. }
  360. if (!$bypassAuth ) {
  361. $res = $this->_cmdAuthenticate($user, $pass, $logintype, $euser);
  362. if (is_a($res, 'PEAR_Error')) {
  363. return $res;
  364. }
  365. }
  366. $this->_state = NET_SIEVE_STATE_TRANSACTION;
  367. return true;
  368. }
  369. /**
  370. * Returns an indexed array of scripts currently on the server.
  371. *
  372. * @return array Indexed array of scriptnames.
  373. */
  374. function listScripts()
  375. {
  376. if (is_array($scripts = $this->_cmdListScripts())) {
  377. return $scripts[0];
  378. } else {
  379. return $scripts;
  380. }
  381. }
  382. /**
  383. * Returns the active script.
  384. *
  385. * @return string The active scriptname.
  386. */
  387. function getActive()
  388. {
  389. if (is_array($scripts = $this->_cmdListScripts())) {
  390. return $scripts[1];
  391. }
  392. }
  393. /**
  394. * Sets the active script.
  395. *
  396. * @param string $scriptname The name of the script to be set as active.
  397. *
  398. * @return boolean True on success, PEAR_Error on failure.
  399. */
  400. function setActive($scriptname)
  401. {
  402. return $this->_cmdSetActive($scriptname);
  403. }
  404. /**
  405. * Retrieves a script.
  406. *
  407. * @param string $scriptname The name of the script to be retrieved.
  408. *
  409. * @return string The script on success, PEAR_Error on failure.
  410. */
  411. function getScript($scriptname)
  412. {
  413. return $this->_cmdGetScript($scriptname);
  414. }
  415. /**
  416. * Adds a script to the server.
  417. *
  418. * @param string $scriptname Name of the script.
  419. * @param string $script The script content.
  420. * @param boolean $makeactive Whether to make this the active script.
  421. *
  422. * @return boolean True on success, PEAR_Error on failure.
  423. */
  424. function installScript($scriptname, $script, $makeactive = false)
  425. {
  426. $res = $this->_cmdPutScript($scriptname, $script);
  427. if (is_a($res, 'PEAR_Error')) {
  428. return $res;
  429. }
  430. if ($makeactive) {
  431. return $this->_cmdSetActive($scriptname);
  432. }
  433. return true;
  434. }
  435. /**
  436. * Removes a script from the server.
  437. *
  438. * @param string $scriptname Name of the script.
  439. *
  440. * @return boolean True on success, PEAR_Error on failure.
  441. */
  442. function removeScript($scriptname)
  443. {
  444. return $this->_cmdDeleteScript($scriptname);
  445. }
  446. /**
  447. * Checks if the server has space to store the script by the server.
  448. *
  449. * @param string $scriptname The name of the script to mark as active.
  450. * @param integer $size The size of the script.
  451. *
  452. * @return boolean|PEAR_Error True if there is space, PEAR_Error otherwise.
  453. *
  454. * @todo Rename to hasSpace()
  455. */
  456. function haveSpace($scriptname, $size)
  457. {
  458. if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  459. return $this->_pear->raiseError('Not currently in TRANSACTION state', 1);
  460. }
  461. $res = $this->_doCmd(sprintf('HAVESPACE %s %d', $this->_escape($scriptname), $size));
  462. if (is_a($res, 'PEAR_Error')) {
  463. return $res;
  464. }
  465. return true;
  466. }
  467. /**
  468. * Returns the list of extensions the server supports.
  469. *
  470. * @return array List of extensions or PEAR_Error on failure.
  471. */
  472. function getExtensions()
  473. {
  474. if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
  475. return $this->_pear->raiseError('Not currently connected', 7);
  476. }
  477. return $this->_capability['extensions'];
  478. }
  479. /**
  480. * Returns whether the server supports an extension.
  481. *
  482. * @param string $extension The extension to check.
  483. *
  484. * @return boolean Whether the extension is supported or PEAR_Error on
  485. * failure.
  486. */
  487. function hasExtension($extension)
  488. {
  489. if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
  490. return $this->_pear->raiseError('Not currently connected', 7);
  491. }
  492. $extension = trim($this->_toUpper($extension));
  493. if (is_array($this->_capability['extensions'])) {
  494. foreach ($this->_capability['extensions'] as $ext) {
  495. if ($ext == $extension) {
  496. return true;
  497. }
  498. }
  499. }
  500. return false;
  501. }
  502. /**
  503. * Returns the list of authentication methods the server supports.
  504. *
  505. * @return array List of authentication methods or PEAR_Error on failure.
  506. */
  507. function getAuthMechs()
  508. {
  509. if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
  510. return $this->_pear->raiseError('Not currently connected', 7);
  511. }
  512. return $this->_capability['sasl'];
  513. }
  514. /**
  515. * Returns whether the server supports an authentication method.
  516. *
  517. * @param string $method The method to check.
  518. *
  519. * @return boolean Whether the method is supported or PEAR_Error on
  520. * failure.
  521. */
  522. function hasAuthMech($method)
  523. {
  524. if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
  525. return $this->_pear->raiseError('Not currently connected', 7);
  526. }
  527. $method = trim($this->_toUpper($method));
  528. if (is_array($this->_capability['sasl'])) {
  529. foreach ($this->_capability['sasl'] as $sasl) {
  530. if ($sasl == $method) {
  531. return true;
  532. }
  533. }
  534. }
  535. return false;
  536. }
  537. /**
  538. * Handles the authentication using any known method.
  539. *
  540. * @param string $uid The userid to authenticate as.
  541. * @param string $pwd The password to authenticate with.
  542. * @param string $userMethod The method to use. If empty, the class chooses
  543. * the best (strongest) available method.
  544. * @param string $euser The effective uid to authenticate as.
  545. *
  546. * @return void
  547. */
  548. function _cmdAuthenticate($uid, $pwd, $userMethod = null, $euser = '')
  549. {
  550. $method = $this->_getBestAuthMethod($userMethod);
  551. if (is_a($method, 'PEAR_Error')) {
  552. return $method;
  553. }
  554. switch ($method) {
  555. case 'DIGEST-MD5':
  556. return $this->_authDigestMD5($uid, $pwd, $euser);
  557. case 'CRAM-MD5':
  558. $result = $this->_authCRAMMD5($uid, $pwd, $euser);
  559. break;
  560. case 'LOGIN':
  561. $result = $this->_authLOGIN($uid, $pwd, $euser);
  562. break;
  563. case 'PLAIN':
  564. $result = $this->_authPLAIN($uid, $pwd, $euser);
  565. break;
  566. case 'EXTERNAL':
  567. $result = $this->_authEXTERNAL($uid, $pwd, $euser);
  568. break;
  569. default :
  570. $result = $this->_pear->raiseError(
  571. $method . ' is not a supported authentication method'
  572. );
  573. break;
  574. }
  575. $res = $this->_doCmd();
  576. if (is_a($res, 'PEAR_Error')) {
  577. return $res;
  578. }
  579. // Query the server capabilities again now that we are authenticated.
  580. if ($this->_pear->isError($res = $this->_cmdCapability())) {
  581. return $this->_pear->raiseError(
  582. 'Failed to connect, server said: ' . $res->getMessage(), 2
  583. );
  584. }
  585. return $result;
  586. }
  587. /**
  588. * Authenticates the user using the PLAIN method.
  589. *
  590. * @param string $user The userid to authenticate as.
  591. * @param string $pass The password to authenticate with.
  592. * @param string $euser The effective uid to authenticate as.
  593. *
  594. * @return void
  595. */
  596. function _authPLAIN($user, $pass, $euser)
  597. {
  598. return $this->_sendCmd(
  599. sprintf(
  600. 'AUTHENTICATE "PLAIN" "%s"',
  601. base64_encode($euser . chr(0) . $user . chr(0) . $pass)
  602. )
  603. );
  604. }
  605. /**
  606. * Authenticates the user using the LOGIN method.
  607. *
  608. * @param string $user The userid to authenticate as.
  609. * @param string $pass The password to authenticate with.
  610. * @param string $euser The effective uid to authenticate as. Not used.
  611. *
  612. * @return void
  613. */
  614. function _authLOGIN($user, $pass, $euser)
  615. {
  616. $result = $this->_sendCmd('AUTHENTICATE "LOGIN"');
  617. if (is_a($result, 'PEAR_Error')) {
  618. return $result;
  619. }
  620. $result = $this->_doCmd('"' . base64_encode($user) . '"', true);
  621. if (is_a($result, 'PEAR_Error')) {
  622. return $result;
  623. }
  624. return $this->_doCmd('"' . base64_encode($pass) . '"', true);
  625. }
  626. /**
  627. * Authenticates the user using the CRAM-MD5 method.
  628. *
  629. * @param string $user The userid to authenticate as.
  630. * @param string $pass The password to authenticate with.
  631. * @param string $euser The effective uid to authenticate as. Not used.
  632. *
  633. * @return void
  634. */
  635. function _authCRAMMD5($user, $pass, $euser)
  636. {
  637. $challenge = $this->_doCmd('AUTHENTICATE "CRAM-MD5"', true);
  638. if (is_a($challenge, 'PEAR_Error')) {
  639. return $challenge;
  640. }
  641. $auth_sasl = new Auth_SASL;
  642. $cram = $auth_sasl->factory('crammd5');
  643. $challenge = base64_decode(trim($challenge));
  644. $response = $cram->getResponse($user, $pass, $challenge);
  645. if (is_a($response, 'PEAR_Error')) {
  646. return $response;
  647. }
  648. return $this->_sendStringResponse(base64_encode($response));
  649. }
  650. /**
  651. * Authenticates the user using the DIGEST-MD5 method.
  652. *
  653. * @param string $user The userid to authenticate as.
  654. * @param string $pass The password to authenticate with.
  655. * @param string $euser The effective uid to authenticate as.
  656. *
  657. * @return void
  658. */
  659. function _authDigestMD5($user, $pass, $euser)
  660. {
  661. $challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"', true);
  662. if (is_a($challenge, 'PEAR_Error')) {
  663. return $challenge;
  664. }
  665. $auth_sasl = new Auth_SASL;
  666. $digest = $auth_sasl->factory('digestmd5');
  667. $challenge = base64_decode(trim($challenge));
  668. // @todo Really 'localhost'?
  669. $response = $digest->getResponse($user, $pass, $challenge, 'localhost', 'sieve', $euser);
  670. if (is_a($response, 'PEAR_Error')) {
  671. return $response;
  672. }
  673. $result = $this->_sendStringResponse(base64_encode($response));
  674. if (is_a($result, 'PEAR_Error')) {
  675. return $result;
  676. }
  677. $result = $this->_doCmd('', true);
  678. if (is_a($result, 'PEAR_Error')) {
  679. return $result;
  680. }
  681. if ($this->_toUpper(substr($result, 0, 2)) == 'OK') {
  682. return;
  683. }
  684. /* We don't use the protocol's third step because SIEVE doesn't allow
  685. * subsequent authentication, so we just silently ignore it. */
  686. $result = $this->_sendStringResponse('');
  687. if (is_a($result, 'PEAR_Error')) {
  688. return $result;
  689. }
  690. return $this->_doCmd();
  691. }
  692. /**
  693. * Authenticates the user using the EXTERNAL method.
  694. *
  695. * @param string $user The userid to authenticate as.
  696. * @param string $pass The password to authenticate with.
  697. * @param string $euser The effective uid to authenticate as.
  698. *
  699. * @return void
  700. *
  701. * @since 1.1.7
  702. */
  703. function _authEXTERNAL($user, $pass, $euser)
  704. {
  705. $cmd = sprintf(
  706. 'AUTHENTICATE "EXTERNAL" "%s"',
  707. base64_encode(strlen($euser) ? $euser : $user)
  708. );
  709. return $this->_sendCmd($cmd);
  710. }
  711. /**
  712. * Removes a script from the server.
  713. *
  714. * @param string $scriptname Name of the script to delete.
  715. *
  716. * @return boolean True on success, PEAR_Error otherwise.
  717. */
  718. function _cmdDeleteScript($scriptname)
  719. {
  720. if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  721. return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
  722. }
  723. $res = $this->_doCmd(sprintf('DELETESCRIPT %s', $this->_escape($scriptname)));
  724. if (is_a($res, 'PEAR_Error')) {
  725. return $res;
  726. }
  727. return true;
  728. }
  729. /**
  730. * Retrieves the contents of the named script.
  731. *
  732. * @param string $scriptname Name of the script to retrieve.
  733. *
  734. * @return string The script if successful, PEAR_Error otherwise.
  735. */
  736. function _cmdGetScript($scriptname)
  737. {
  738. if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  739. return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
  740. }
  741. $res = $this->_doCmd(sprintf('GETSCRIPT %s', $this->_escape($scriptname)));
  742. if (is_a($res, 'PEAR_Error')) {
  743. return $res;
  744. }
  745. return preg_replace('/^{[0-9]+}\r\n/', '', $res);
  746. }
  747. /**
  748. * Sets the active script, i.e. the one that gets run on new mail by the
  749. * server.
  750. *
  751. * @param string $scriptname The name of the script to mark as active.
  752. *
  753. * @return boolean True on success, PEAR_Error otherwise.
  754. */
  755. function _cmdSetActive($scriptname)
  756. {
  757. if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  758. return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
  759. }
  760. $res = $this->_doCmd(sprintf('SETACTIVE %s', $this->_escape($scriptname)));
  761. if (is_a($res, 'PEAR_Error')) {
  762. return $res;
  763. }
  764. return true;
  765. }
  766. /**
  767. * Returns the list of scripts on the server.
  768. *
  769. * @return array An array with the list of scripts in the first element
  770. * and the active script in the second element on success,
  771. * PEAR_Error otherwise.
  772. */
  773. function _cmdListScripts()
  774. {
  775. if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  776. return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
  777. }
  778. $res = $this->_doCmd('LISTSCRIPTS');
  779. if (is_a($res, 'PEAR_Error')) {
  780. return $res;
  781. }
  782. $scripts = array();
  783. $activescript = null;
  784. $res = explode("\r\n", $res);
  785. foreach ($res as $value) {
  786. if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) {
  787. $script_name = stripslashes($matches[1]);
  788. $scripts[] = $script_name;
  789. if (!empty($matches[2])) {
  790. $activescript = $script_name;
  791. }
  792. }
  793. }
  794. return array($scripts, $activescript);
  795. }
  796. /**
  797. * Adds a script to the server.
  798. *
  799. * @param string $scriptname Name of the new script.
  800. * @param string $scriptdata The new script.
  801. *
  802. * @return boolean True on success, PEAR_Error otherwise.
  803. */
  804. function _cmdPutScript($scriptname, $scriptdata)
  805. {
  806. if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
  807. return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
  808. }
  809. $stringLength = $this->_getLineLength($scriptdata);
  810. $command = sprintf(
  811. "PUTSCRIPT %s {%d+}\r\n%s",
  812. $this->_escape($scriptname),
  813. $stringLength,
  814. $scriptdata
  815. );
  816. $res = $this->_doCmd($command);
  817. if (is_a($res, 'PEAR_Error')) {
  818. return $res;
  819. }
  820. return true;
  821. }
  822. /**
  823. * Logs out of the server and terminates the connection.
  824. *
  825. * @param boolean $sendLogoutCMD Whether to send LOGOUT command before
  826. * disconnecting.
  827. *
  828. * @return boolean True on success, PEAR_Error otherwise.
  829. */
  830. function _cmdLogout($sendLogoutCMD = true)
  831. {
  832. if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
  833. return $this->_pear->raiseError('Not currently connected', 1);
  834. }
  835. if ($sendLogoutCMD) {
  836. $res = $this->_doCmd('LOGOUT');
  837. if (is_a($res, 'PEAR_Error')) {
  838. return $res;
  839. }
  840. }
  841. $this->_sock->disconnect();
  842. $this->_state = NET_SIEVE_STATE_DISCONNECTED;
  843. return true;
  844. }
  845. /**
  846. * Sends the CAPABILITY command
  847. *
  848. * @return boolean True on success, PEAR_Error otherwise.
  849. */
  850. function _cmdCapability()
  851. {
  852. if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
  853. return $this->_pear->raiseError('Not currently connected', 1);
  854. }
  855. $res = $this->_doCmd('CAPABILITY');
  856. if (is_a($res, 'PEAR_Error')) {
  857. return $res;
  858. }
  859. $this->_parseCapability($res);
  860. return true;
  861. }
  862. /**
  863. * Parses the response from the CAPABILITY command and stores the result
  864. * in $_capability.
  865. *
  866. * @param string $data The response from the capability command.
  867. *
  868. * @return void
  869. */
  870. function _parseCapability($data)
  871. {
  872. // Clear the cached capabilities.
  873. $this->_capability = array('sasl' => array(),
  874. 'extensions' => array());
  875. $data = preg_split('/\r?\n/', $this->_toUpper($data), -1, PREG_SPLIT_NO_EMPTY);
  876. for ($i = 0; $i < count($data); $i++) {
  877. if (!preg_match('/^"([A-Z]+)"( "(.*)")?$/', $data[$i], $matches)) {
  878. continue;
  879. }
  880. switch ($matches[1]) {
  881. case 'IMPLEMENTATION':
  882. $this->_capability['implementation'] = $matches[3];
  883. break;
  884. case 'SASL':
  885. $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
  886. break;
  887. case 'SIEVE':
  888. $this->_capability['extensions'] = preg_split('/\s+/', $matches[3]);
  889. break;
  890. case 'STARTTLS':
  891. $this->_capability['starttls'] = true;
  892. break;
  893. }
  894. }
  895. }
  896. /**
  897. * Sends a command to the server
  898. *
  899. * @param string $cmd The command to send.
  900. *
  901. * @return void
  902. */
  903. function _sendCmd($cmd)
  904. {
  905. $status = $this->_sock->getStatus();
  906. if (is_a($status, 'PEAR_Error') || $status['eof']) {
  907. return $this->_pear->raiseError('Failed to write to socket: connection lost');
  908. }
  909. $error = $this->_sock->write($cmd . "\r\n");
  910. if (is_a($error, 'PEAR_Error')) {
  911. return $this->_pear->raiseError(
  912. 'Failed to write to socket: ' . $error->getMessage()
  913. );
  914. }
  915. $this->_debug("C: $cmd");
  916. }
  917. /**
  918. * Sends a string response to the server.
  919. *
  920. * @param string $str The string to send.
  921. *
  922. * @return void
  923. */
  924. function _sendStringResponse($str)
  925. {
  926. return $this->_sendCmd('{' . $this->_getLineLength($str) . "+}\r\n" . $str);
  927. }
  928. /**
  929. * Receives a single line from the server.
  930. *
  931. * @return string The server response line.
  932. */
  933. function _recvLn()
  934. {
  935. $lastline = $this->_sock->gets(8192);
  936. if (is_a($lastline, 'PEAR_Error')) {
  937. return $this->_pear->raiseError(
  938. 'Failed to read from socket: ' . $lastline->getMessage()
  939. );
  940. }
  941. $lastline = rtrim($lastline);
  942. $this->_debug("S: $lastline");
  943. if ($lastline === '') {
  944. return $this->_pear->raiseError('Failed to read from socket');
  945. }
  946. return $lastline;
  947. }
  948. /**
  949. * Receives a number of bytes from the server.
  950. *
  951. * @param integer $length Number of bytes to read.
  952. *
  953. * @return string The server response.
  954. */
  955. function _recvBytes($length)
  956. {
  957. $response = '';
  958. $response_length = 0;
  959. while ($response_length < $length) {
  960. $response .= $this->_sock->read($length - $response_length);
  961. $response_length = $this->_getLineLength($response);
  962. }
  963. $this->_debug('S: ' . rtrim($response));
  964. return $response;
  965. }
  966. /**
  967. * Send a command and retrieves a response from the server.
  968. *
  969. * @param string $cmd The command to send.
  970. * @param boolean $auth Whether this is an authentication command.
  971. *
  972. * @return string|PEAR_Error Reponse string if an OK response, PEAR_Error
  973. * if a NO response.
  974. */
  975. function _doCmd($cmd = '', $auth = false)
  976. {
  977. $referralCount = 0;
  978. while ($referralCount < $this->_maxReferralCount) {
  979. if (strlen($cmd)) {
  980. $error = $this->_sendCmd($cmd);
  981. if (is_a($error, 'PEAR_Error')) {
  982. return $error;
  983. }
  984. }
  985. $response = '';
  986. while (true) {
  987. $line = $this->_recvLn();
  988. if (is_a($line, 'PEAR_Error')) {
  989. return $line;
  990. }
  991. if (preg_match('/^(OK|NO)/i', $line, $tag)) {
  992. // Check for string literal message.
  993. if (preg_match('/{([0-9]+)}$/', $line, $matches)) {
  994. $line = substr($line, 0, -(strlen($matches[1]) + 2))
  995. . str_replace(
  996. "\r\n", ' ', $this->_recvBytes($matches[1] + 2)
  997. );
  998. }
  999. if ('OK' == $this->_toUpper($tag[1])) {
  1000. $response .= $line;
  1001. return rtrim($response);
  1002. }
  1003. return $this->_pear->raiseError(trim($response . substr($line, 2)), 3);
  1004. }
  1005. if (preg_match('/^BYE/i', $line)) {
  1006. $error = $this->disconnect(false);
  1007. if (is_a($error, 'PEAR_Error')) {
  1008. return $this->_pear->raiseError(
  1009. 'Cannot handle BYE, the error was: '
  1010. . $error->getMessage(),
  1011. 4
  1012. );
  1013. }
  1014. // Check for referral, then follow it. Otherwise, carp an
  1015. // error.
  1016. if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
  1017. // Replace the old host with the referral host
  1018. // preserving any protocol prefix.
  1019. $this->_data['host'] = preg_replace(
  1020. '/\w+(?!(\w|\:\/\/)).*/', $matches[2],
  1021. $this->_data['host']
  1022. );
  1023. $error = $this->_handleConnectAndLogin();
  1024. if (is_a($error, 'PEAR_Error')) {
  1025. return $this->_pear->raiseError(
  1026. 'Cannot follow referral to '
  1027. . $this->_data['host'] . ', the error was: '
  1028. . $error->getMessage(),
  1029. 5
  1030. );
  1031. }
  1032. break;
  1033. }
  1034. return $this->_pear->raiseError(trim($response . $line), 6);
  1035. }
  1036. if (preg_match('/^{([0-9]+)}/', $line, $matches)) {
  1037. // Matches literal string responses.
  1038. $line = $this->_recvBytes($matches[1] + 2);
  1039. if (!$auth) {
  1040. // Receive the pending OK only if we aren't
  1041. // authenticating since string responses during
  1042. // authentication don't need an OK.
  1043. $this->_recvLn();
  1044. }
  1045. return $line;
  1046. }
  1047. if ($auth) {
  1048. // String responses during authentication don't need an
  1049. // OK.
  1050. $response .= $line;
  1051. return rtrim($response);
  1052. }
  1053. $response .= $line . "\r\n";
  1054. $referralCount++;
  1055. }
  1056. }
  1057. return $this->_pear->raiseError('Max referral count (' . $referralCount . ') reached. Cyrus murder loop error?', 7);
  1058. }
  1059. /**
  1060. * Returns the name of the best authentication method that the server
  1061. * has advertised.
  1062. *
  1063. * @param string $userMethod Only consider this method as available.
  1064. *
  1065. * @return string The name of the best supported authentication method or
  1066. * a PEAR_Error object on failure.
  1067. */
  1068. function _getBestAuthMethod($userMethod = null)
  1069. {
  1070. if (!isset($this->_capability['sasl'])) {
  1071. return $this->_pear->raiseError('This server doesn\'t support any authentication methods. SASL problem?');
  1072. }
  1073. if (!$this->_capability['sasl']) {
  1074. return $this->_pear->raiseError('This server doesn\'t support any authentication methods.');
  1075. }
  1076. if ($userMethod) {
  1077. if (in_array($userMethod, $this->_capability['sasl'])) {
  1078. return $userMethod;
  1079. }
  1080. $msg = 'No supported authentication method found. The server supports these methods: %s, but we want to use: %s';
  1081. return $this->_pear->raiseError(
  1082. sprintf($msg, implode(', ', $this->_capability['sasl']), $userMethod)
  1083. );
  1084. }
  1085. foreach ($this->supportedAuthMethods as $method) {
  1086. if (in_array($method, $this->_capability['sasl'])) {
  1087. return $method;
  1088. }
  1089. }
  1090. $msg = 'No supported authentication method found. The server supports these methods: %s, but we only support: %s';
  1091. return $this->_pear->raiseError(
  1092. sprintf($msg, implode(', ', $this->_capability['sasl']), implode(', ', $this->supportedAuthMethods))
  1093. );
  1094. }
  1095. /**
  1096. * Starts a TLS connection.
  1097. *
  1098. * @return boolean True on success, PEAR_Error on failure.
  1099. */
  1100. function _startTLS()
  1101. {
  1102. $res = $this->_doCmd('STARTTLS');
  1103. if (is_a($res, 'PEAR_Error')) {
  1104. return $res;
  1105. }
  1106. if (isset($this->_options['ssl']['crypto_method'])) {
  1107. $crypto_method = $this->_options['ssl']['crypto_method'];
  1108. }
  1109. else {
  1110. // There is no flag to enable all TLS methods. Net_SMTP
  1111. // handles enabling TLS similarly.
  1112. $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT
  1113. | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
  1114. | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
  1115. }
  1116. if (!stream_socket_enable_crypto($this->_sock->fp, true, $crypto_method)) {
  1117. return $this->_pear->raiseError('Failed to establish TLS connection', 2);
  1118. }
  1119. $this->_debug('STARTTLS negotiation successful');
  1120. // The server should be sending a CAPABILITY response after
  1121. // negotiating TLS. Read it, and ignore if it doesn't.
  1122. // Unfortunately old Cyrus versions are broken and don't send a
  1123. // CAPABILITY response, thus we would wait here forever. Parse the
  1124. // Cyrus version and work around this broken behavior.
  1125. if (!preg_match('/^CYRUS TIMSIEVED V([0-9.]+)/', $this->_capability['implementation'], $matches)
  1126. || version_compare($matches[1], '2.3.10', '>=')
  1127. ) {
  1128. $this->_doCmd();
  1129. }
  1130. // Query the server capabilities again now that we are under
  1131. // encryption.
  1132. $res = $this->_cmdCapability();
  1133. if (is_a($res, 'PEAR_Error')) {
  1134. return $this->_pear->raiseError(
  1135. 'Failed to connect, server said: ' . $res->getMessage(), 2
  1136. );
  1137. }
  1138. return true;
  1139. }
  1140. /**
  1141. * Returns the length of a string.
  1142. *
  1143. * @param string $string A string.
  1144. *
  1145. * @return integer The length of the string.
  1146. */
  1147. function _getLineLength($string)
  1148. {
  1149. if (extension_loaded('mbstring')) {
  1150. return mb_strlen($string, 'latin1');
  1151. } else {
  1152. return strlen($string);
  1153. }
  1154. }
  1155. /**
  1156. * Locale independant strtoupper() implementation.
  1157. *
  1158. * @param string $string The string to convert to lowercase.
  1159. *
  1160. * @return string The lowercased string, based on ASCII encoding.
  1161. */
  1162. function _toUpper($string)
  1163. {
  1164. $language = setlocale(LC_CTYPE, 0);
  1165. setlocale(LC_CTYPE, 'C');
  1166. $string = strtoupper($string);
  1167. setlocale(LC_CTYPE, $language);
  1168. return $string;
  1169. }
  1170. /**
  1171. * Converts strings into RFC's quoted-string or literal-c2s form.
  1172. *
  1173. * @param string $string The string to convert.
  1174. *
  1175. * @return string Result string.
  1176. */
  1177. function _escape($string)
  1178. {
  1179. // Some implementations don't allow UTF-8 characters in quoted-string,
  1180. // use literal-c2s.
  1181. if (preg_match('/[^\x01-\x09\x0B-\x0C\x0E-\x7F]/', $string)) {
  1182. return sprintf("{%d+}\r\n%s", $this->_getLineLength($string), $string);
  1183. }
  1184. return '"' . addcslashes($string, '\\"') . '"';
  1185. }
  1186. /**
  1187. * Write debug text to the current debug output handler.
  1188. *
  1189. * @param string $message Debug message text.
  1190. *
  1191. * @return void
  1192. */
  1193. function _debug($message)
  1194. {
  1195. if ($this->_debug) {
  1196. if ($this->_debug_handler) {
  1197. call_user_func_array($this->_debug_handler, array(&$this, $message));
  1198. } else {
  1199. echo "$message\n";
  1200. }
  1201. }
  1202. }
  1203. }