You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Sieve.php 45KB

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