SMTP.php 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
  1. <?php
  2. /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Chuck Hagenbuch <chuck@horde.org> |
  17. // | Jon Parise <jon@php.net> |
  18. // | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: SMTP.php 293948 2010-01-24 21:46:00Z jon $
  22. require_once 'PEAR.php';
  23. require_once 'Net/Socket.php';
  24. /**
  25. * Provides an implementation of the SMTP protocol using PEAR's
  26. * Net_Socket:: class.
  27. *
  28. * @package Net_SMTP
  29. * @author Chuck Hagenbuch <chuck@horde.org>
  30. * @author Jon Parise <jon@php.net>
  31. * @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
  32. *
  33. * @example basic.php A basic implementation of the Net_SMTP package.
  34. */
  35. class Net_SMTP
  36. {
  37. /**
  38. * The server to connect to.
  39. * @var string
  40. * @access public
  41. */
  42. var $host = 'localhost';
  43. /**
  44. * The port to connect to.
  45. * @var int
  46. * @access public
  47. */
  48. var $port = 25;
  49. /**
  50. * The value to give when sending EHLO or HELO.
  51. * @var string
  52. * @access public
  53. */
  54. var $localhost = 'localhost';
  55. /**
  56. * List of supported authentication methods, in preferential order.
  57. * @var array
  58. * @access public
  59. */
  60. var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
  61. /**
  62. * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
  63. * server supports it.
  64. *
  65. * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
  66. * somlFrom() and samlFrom() do not wait for a response from the
  67. * SMTP server but return immediately.
  68. *
  69. * @var bool
  70. * @access public
  71. */
  72. var $pipelining = false;
  73. /**
  74. * Number of pipelined commands.
  75. * @var int
  76. * @access private
  77. */
  78. var $_pipelined_commands = 0;
  79. /**
  80. * Should debugging output be enabled?
  81. * @var boolean
  82. * @access private
  83. */
  84. var $_debug = false;
  85. /**
  86. * Debug output handler.
  87. * @var callback
  88. * @access private
  89. */
  90. var $_debug_handler = null;
  91. /**
  92. * The socket resource being used to connect to the SMTP server.
  93. * @var resource
  94. * @access private
  95. */
  96. var $_socket = null;
  97. /**
  98. * The most recent server response code.
  99. * @var int
  100. * @access private
  101. */
  102. var $_code = -1;
  103. /**
  104. * The most recent server response arguments.
  105. * @var array
  106. * @access private
  107. */
  108. var $_arguments = array();
  109. /**
  110. * Stores the SMTP server's greeting string.
  111. * @var string
  112. * @access private
  113. */
  114. var $_greeting = null;
  115. /**
  116. * Stores detected features of the SMTP server.
  117. * @var array
  118. * @access private
  119. */
  120. var $_esmtp = array();
  121. /**
  122. * Instantiates a new Net_SMTP object, overriding any defaults
  123. * with parameters that are passed in.
  124. *
  125. * If you have SSL support in PHP, you can connect to a server
  126. * over SSL using an 'ssl://' prefix:
  127. *
  128. * // 465 is a common smtps port.
  129. * $smtp = new Net_SMTP('ssl://mail.host.com', 465);
  130. * $smtp->connect();
  131. *
  132. * @param string $host The server to connect to.
  133. * @param integer $port The port to connect to.
  134. * @param string $localhost The value to give when sending EHLO or HELO.
  135. * @param boolean $pipeling Use SMTP command pipelining
  136. *
  137. * @access public
  138. * @since 1.0
  139. */
  140. function Net_SMTP($host = null, $port = null, $localhost = null, $pipelining = false)
  141. {
  142. if (isset($host)) {
  143. $this->host = $host;
  144. }
  145. if (isset($port)) {
  146. $this->port = $port;
  147. }
  148. if (isset($localhost)) {
  149. $this->localhost = $localhost;
  150. }
  151. $this->pipelining = $pipelining;
  152. $this->_socket = new Net_Socket();
  153. /* Include the Auth_SASL package. If the package is not
  154. * available, we disable the authentication methods that
  155. * depend upon it. */
  156. if ((@include_once 'Auth/SASL.php') === false) {
  157. $pos = array_search('DIGEST-MD5', $this->auth_methods);
  158. unset($this->auth_methods[$pos]);
  159. $pos = array_search('CRAM-MD5', $this->auth_methods);
  160. unset($this->auth_methods[$pos]);
  161. }
  162. }
  163. /**
  164. * Set the value of the debugging flag.
  165. *
  166. * @param boolean $debug New value for the debugging flag.
  167. *
  168. * @access public
  169. * @since 1.1.0
  170. */
  171. function setDebug($debug, $handler = null)
  172. {
  173. $this->_debug = $debug;
  174. $this->_debug_handler = $handler;
  175. }
  176. /**
  177. * Write the given debug text to the current debug output handler.
  178. *
  179. * @param string $message Debug mesage text.
  180. *
  181. * @access private
  182. * @since 1.3.3
  183. */
  184. function _debug($message)
  185. {
  186. if ($this->_debug) {
  187. if ($this->_debug_handler) {
  188. call_user_func_array($this->_debug_handler,
  189. array(&$this, $message));
  190. } else {
  191. echo "DEBUG: $message\n";
  192. }
  193. }
  194. }
  195. /**
  196. * Send the given string of data to the server.
  197. *
  198. * @param string $data The string of data to send.
  199. *
  200. * @return mixed True on success or a PEAR_Error object on failure.
  201. *
  202. * @access private
  203. * @since 1.1.0
  204. */
  205. function _send($data)
  206. {
  207. $this->_debug("Send: $data");
  208. $error = $this->_socket->write($data);
  209. if ($error === false || PEAR::isError($error)) {
  210. $msg = ($error) ? $error->getMessage() : "unknown error";
  211. return PEAR::raiseError("Failed to write to socket: $msg");
  212. }
  213. return true;
  214. }
  215. /**
  216. * Send a command to the server with an optional string of
  217. * arguments. A carriage return / linefeed (CRLF) sequence will
  218. * be appended to each command string before it is sent to the
  219. * SMTP server - an error will be thrown if the command string
  220. * already contains any newline characters. Use _send() for
  221. * commands that must contain newlines.
  222. *
  223. * @param string $command The SMTP command to send to the server.
  224. * @param string $args A string of optional arguments to append
  225. * to the command.
  226. *
  227. * @return mixed The result of the _send() call.
  228. *
  229. * @access private
  230. * @since 1.1.0
  231. */
  232. function _put($command, $args = '')
  233. {
  234. if (!empty($args)) {
  235. $command .= ' ' . $args;
  236. }
  237. if (strcspn($command, "\r\n") !== strlen($command)) {
  238. return PEAR::raiseError('Commands cannot contain newlines');
  239. }
  240. return $this->_send($command . "\r\n");
  241. }
  242. /**
  243. * Read a reply from the SMTP server. The reply consists of a response
  244. * code and a response message.
  245. *
  246. * @param mixed $valid The set of valid response codes. These
  247. * may be specified as an array of integer
  248. * values or as a single integer value.
  249. * @param bool $later Do not parse the response now, but wait
  250. * until the last command in the pipelined
  251. * command group
  252. *
  253. * @return mixed True if the server returned a valid response code or
  254. * a PEAR_Error object is an error condition is reached.
  255. *
  256. * @access private
  257. * @since 1.1.0
  258. *
  259. * @see getResponse
  260. */
  261. function _parseResponse($valid, $later = false)
  262. {
  263. $this->_code = -1;
  264. $this->_arguments = array();
  265. if ($later) {
  266. $this->_pipelined_commands++;
  267. return true;
  268. }
  269. for ($i = 0; $i <= $this->_pipelined_commands; $i++) {
  270. while ($line = $this->_socket->readLine()) {
  271. $this->_debug("Recv: $line");
  272. /* If we receive an empty line, the connection has been closed. */
  273. if (empty($line)) {
  274. $this->disconnect();
  275. return PEAR::raiseError('Connection was unexpectedly closed');
  276. }
  277. /* Read the code and store the rest in the arguments array. */
  278. $code = substr($line, 0, 3);
  279. $this->_arguments[] = trim(substr($line, 4));
  280. /* Check the syntax of the response code. */
  281. if (is_numeric($code)) {
  282. $this->_code = (int)$code;
  283. } else {
  284. $this->_code = -1;
  285. break;
  286. }
  287. /* If this is not a multiline response, we're done. */
  288. if (substr($line, 3, 1) != '-') {
  289. break;
  290. }
  291. }
  292. }
  293. $this->_pipelined_commands = 0;
  294. /* Compare the server's response code with the valid code/codes. */
  295. if (is_int($valid) && ($this->_code === $valid)) {
  296. return true;
  297. } elseif (is_array($valid) && in_array($this->_code, $valid, true)) {
  298. return true;
  299. }
  300. return PEAR::raiseError('Invalid response code received from server',
  301. $this->_code);
  302. }
  303. /**
  304. * Return a 2-tuple containing the last response from the SMTP server.
  305. *
  306. * @return array A two-element array: the first element contains the
  307. * response code as an integer and the second element
  308. * contains the response's arguments as a string.
  309. *
  310. * @access public
  311. * @since 1.1.0
  312. */
  313. function getResponse()
  314. {
  315. return array($this->_code, join("\n", $this->_arguments));
  316. }
  317. /**
  318. * Return the SMTP server's greeting string.
  319. *
  320. * @return string A string containing the greeting string, or null if a
  321. * greeting has not been received.
  322. *
  323. * @access public
  324. * @since 1.3.3
  325. */
  326. function getGreeting()
  327. {
  328. return $this->_greeting;
  329. }
  330. /**
  331. * Attempt to connect to the SMTP server.
  332. *
  333. * @param int $timeout The timeout value (in seconds) for the
  334. * socket connection.
  335. * @param bool $persistent Should a persistent socket connection
  336. * be used?
  337. *
  338. * @return mixed Returns a PEAR_Error with an error message on any
  339. * kind of failure, or true on success.
  340. * @access public
  341. * @since 1.0
  342. */
  343. function connect($timeout = null, $persistent = false)
  344. {
  345. $this->_greeting = null;
  346. $result = $this->_socket->connect($this->host, $this->port,
  347. $persistent, $timeout);
  348. if (PEAR::isError($result)) {
  349. return PEAR::raiseError('Failed to connect socket: ' .
  350. $result->getMessage());
  351. }
  352. if (PEAR::isError($error = $this->_parseResponse(220))) {
  353. return $error;
  354. }
  355. /* Extract and store a copy of the server's greeting string. */
  356. list(, $this->_greeting) = $this->getResponse();
  357. if (PEAR::isError($error = $this->_negotiate())) {
  358. return $error;
  359. }
  360. return true;
  361. }
  362. /**
  363. * Attempt to disconnect from the SMTP server.
  364. *
  365. * @return mixed Returns a PEAR_Error with an error message on any
  366. * kind of failure, or true on success.
  367. * @access public
  368. * @since 1.0
  369. */
  370. function disconnect()
  371. {
  372. if (PEAR::isError($error = $this->_put('QUIT'))) {
  373. return $error;
  374. }
  375. if (PEAR::isError($error = $this->_parseResponse(221))) {
  376. return $error;
  377. }
  378. if (PEAR::isError($error = $this->_socket->disconnect())) {
  379. return PEAR::raiseError('Failed to disconnect socket: ' .
  380. $error->getMessage());
  381. }
  382. return true;
  383. }
  384. /**
  385. * Attempt to send the EHLO command and obtain a list of ESMTP
  386. * extensions available, and failing that just send HELO.
  387. *
  388. * @return mixed Returns a PEAR_Error with an error message on any
  389. * kind of failure, or true on success.
  390. *
  391. * @access private
  392. * @since 1.1.0
  393. */
  394. function _negotiate()
  395. {
  396. if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
  397. return $error;
  398. }
  399. if (PEAR::isError($this->_parseResponse(250))) {
  400. /* If we receive a 503 response, we're already authenticated. */
  401. if ($this->_code === 503) {
  402. return true;
  403. }
  404. /* If the EHLO failed, try the simpler HELO command. */
  405. if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
  406. return $error;
  407. }
  408. if (PEAR::isError($this->_parseResponse(250))) {
  409. return PEAR::raiseError('HELO was not accepted: ', $this->_code);
  410. }
  411. return true;
  412. }
  413. foreach ($this->_arguments as $argument) {
  414. $verb = strtok($argument, ' ');
  415. $arguments = substr($argument, strlen($verb) + 1,
  416. strlen($argument) - strlen($verb) - 1);
  417. $this->_esmtp[$verb] = $arguments;
  418. }
  419. if (!isset($this->_esmtp['PIPELINING'])) {
  420. $this->pipelining = false;
  421. }
  422. return true;
  423. }
  424. /**
  425. * Returns the name of the best authentication method that the server
  426. * has advertised.
  427. *
  428. * @return mixed Returns a string containing the name of the best
  429. * supported authentication method or a PEAR_Error object
  430. * if a failure condition is encountered.
  431. * @access private
  432. * @since 1.1.0
  433. */
  434. function _getBestAuthMethod()
  435. {
  436. $available_methods = explode(' ', $this->_esmtp['AUTH']);
  437. foreach ($this->auth_methods as $method) {
  438. if (in_array($method, $available_methods)) {
  439. return $method;
  440. }
  441. }
  442. return PEAR::raiseError('No supported authentication methods');
  443. }
  444. /**
  445. * Attempt to do SMTP authentication.
  446. *
  447. * @param string The userid to authenticate as.
  448. * @param string The password to authenticate with.
  449. * @param string The requested authentication method. If none is
  450. * specified, the best supported method will be used.
  451. * @param bool Flag indicating whether or not TLS should be attempted.
  452. *
  453. * @return mixed Returns a PEAR_Error with an error message on any
  454. * kind of failure, or true on success.
  455. * @access public
  456. * @since 1.0
  457. */
  458. function auth($uid, $pwd , $method = '', $tls = true)
  459. {
  460. /* We can only attempt a TLS connection if one has been requested,
  461. * we're running PHP 5.1.0 or later, have access to the OpenSSL
  462. * extension, are connected to an SMTP server which supports the
  463. * STARTTLS extension, and aren't already connected over a secure
  464. * (SSL) socket connection. */
  465. if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') &&
  466. extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) &&
  467. strncasecmp($this->host, 'ssl://', 6) !== 0) {
  468. /* Start the TLS connection attempt. */
  469. if (PEAR::isError($result = $this->_put('STARTTLS'))) {
  470. return $result;
  471. }
  472. if (PEAR::isError($result = $this->_parseResponse(220))) {
  473. return $result;
  474. }
  475. if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) {
  476. return $result;
  477. } elseif ($result !== true) {
  478. return PEAR::raiseError('STARTTLS failed');
  479. }
  480. /* Send EHLO again to recieve the AUTH string from the
  481. * SMTP server. */
  482. $this->_negotiate();
  483. }
  484. if (empty($this->_esmtp['AUTH'])) {
  485. return PEAR::raiseError('SMTP server does not support authentication');
  486. }
  487. /* If no method has been specified, get the name of the best
  488. * supported method advertised by the SMTP server. */
  489. if (empty($method)) {
  490. if (PEAR::isError($method = $this->_getBestAuthMethod())) {
  491. /* Return the PEAR_Error object from _getBestAuthMethod(). */
  492. return $method;
  493. }
  494. } else {
  495. $method = strtoupper($method);
  496. if (!in_array($method, $this->auth_methods)) {
  497. return PEAR::raiseError("$method is not a supported authentication method");
  498. }
  499. }
  500. switch ($method) {
  501. case 'DIGEST-MD5':
  502. $result = $this->_authDigest_MD5($uid, $pwd);
  503. break;
  504. case 'CRAM-MD5':
  505. $result = $this->_authCRAM_MD5($uid, $pwd);
  506. break;
  507. case 'LOGIN':
  508. $result = $this->_authLogin($uid, $pwd);
  509. break;
  510. case 'PLAIN':
  511. $result = $this->_authPlain($uid, $pwd);
  512. break;
  513. default:
  514. $result = PEAR::raiseError("$method is not a supported authentication method");
  515. break;
  516. }
  517. /* If an error was encountered, return the PEAR_Error object. */
  518. if (PEAR::isError($result)) {
  519. return $result;
  520. }
  521. return true;
  522. }
  523. /**
  524. * Authenticates the user using the DIGEST-MD5 method.
  525. *
  526. * @param string The userid to authenticate as.
  527. * @param string The password to authenticate with.
  528. *
  529. * @return mixed Returns a PEAR_Error with an error message on any
  530. * kind of failure, or true on success.
  531. * @access private
  532. * @since 1.1.0
  533. */
  534. function _authDigest_MD5($uid, $pwd)
  535. {
  536. if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
  537. return $error;
  538. }
  539. /* 334: Continue authentication request */
  540. if (PEAR::isError($error = $this->_parseResponse(334))) {
  541. /* 503: Error: already authenticated */
  542. if ($this->_code === 503) {
  543. return true;
  544. }
  545. return $error;
  546. }
  547. $challenge = base64_decode($this->_arguments[0]);
  548. $digest = &Auth_SASL::factory('digestmd5');
  549. $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
  550. $this->host, "smtp"));
  551. if (PEAR::isError($error = $this->_put($auth_str))) {
  552. return $error;
  553. }
  554. /* 334: Continue authentication request */
  555. if (PEAR::isError($error = $this->_parseResponse(334))) {
  556. return $error;
  557. }
  558. /* We don't use the protocol's third step because SMTP doesn't
  559. * allow subsequent authentication, so we just silently ignore
  560. * it. */
  561. if (PEAR::isError($error = $this->_put(''))) {
  562. return $error;
  563. }
  564. /* 235: Authentication successful */
  565. if (PEAR::isError($error = $this->_parseResponse(235))) {
  566. return $error;
  567. }
  568. }
  569. /**
  570. * Authenticates the user using the CRAM-MD5 method.
  571. *
  572. * @param string The userid to authenticate as.
  573. * @param string The password to authenticate with.
  574. *
  575. * @return mixed Returns a PEAR_Error with an error message on any
  576. * kind of failure, or true on success.
  577. * @access private
  578. * @since 1.1.0
  579. */
  580. function _authCRAM_MD5($uid, $pwd)
  581. {
  582. if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
  583. return $error;
  584. }
  585. /* 334: Continue authentication request */
  586. if (PEAR::isError($error = $this->_parseResponse(334))) {
  587. /* 503: Error: already authenticated */
  588. if ($this->_code === 503) {
  589. return true;
  590. }
  591. return $error;
  592. }
  593. $challenge = base64_decode($this->_arguments[0]);
  594. $cram = &Auth_SASL::factory('crammd5');
  595. $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
  596. if (PEAR::isError($error = $this->_put($auth_str))) {
  597. return $error;
  598. }
  599. /* 235: Authentication successful */
  600. if (PEAR::isError($error = $this->_parseResponse(235))) {
  601. return $error;
  602. }
  603. }
  604. /**
  605. * Authenticates the user using the LOGIN method.
  606. *
  607. * @param string The userid to authenticate as.
  608. * @param string The password to authenticate with.
  609. *
  610. * @return mixed Returns a PEAR_Error with an error message on any
  611. * kind of failure, or true on success.
  612. * @access private
  613. * @since 1.1.0
  614. */
  615. function _authLogin($uid, $pwd)
  616. {
  617. if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
  618. return $error;
  619. }
  620. /* 334: Continue authentication request */
  621. if (PEAR::isError($error = $this->_parseResponse(334))) {
  622. /* 503: Error: already authenticated */
  623. if ($this->_code === 503) {
  624. return true;
  625. }
  626. return $error;
  627. }
  628. if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
  629. return $error;
  630. }
  631. /* 334: Continue authentication request */
  632. if (PEAR::isError($error = $this->_parseResponse(334))) {
  633. return $error;
  634. }
  635. if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
  636. return $error;
  637. }
  638. /* 235: Authentication successful */
  639. if (PEAR::isError($error = $this->_parseResponse(235))) {
  640. return $error;
  641. }
  642. return true;
  643. }
  644. /**
  645. * Authenticates the user using the PLAIN method.
  646. *
  647. * @param string The userid to authenticate as.
  648. * @param string The password to authenticate with.
  649. *
  650. * @return mixed Returns a PEAR_Error with an error message on any
  651. * kind of failure, or true on success.
  652. * @access private
  653. * @since 1.1.0
  654. */
  655. function _authPlain($uid, $pwd)
  656. {
  657. if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
  658. return $error;
  659. }
  660. /* 334: Continue authentication request */
  661. if (PEAR::isError($error = $this->_parseResponse(334))) {
  662. /* 503: Error: already authenticated */
  663. if ($this->_code === 503) {
  664. return true;
  665. }
  666. return $error;
  667. }
  668. $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
  669. if (PEAR::isError($error = $this->_put($auth_str))) {
  670. return $error;
  671. }
  672. /* 235: Authentication successful */
  673. if (PEAR::isError($error = $this->_parseResponse(235))) {
  674. return $error;
  675. }
  676. return true;
  677. }
  678. /**
  679. * Send the HELO command.
  680. *
  681. * @param string The domain name to say we are.
  682. *
  683. * @return mixed Returns a PEAR_Error with an error message on any
  684. * kind of failure, or true on success.
  685. * @access public
  686. * @since 1.0
  687. */
  688. function helo($domain)
  689. {
  690. if (PEAR::isError($error = $this->_put('HELO', $domain))) {
  691. return $error;
  692. }
  693. if (PEAR::isError($error = $this->_parseResponse(250))) {
  694. return $error;
  695. }
  696. return true;
  697. }
  698. /**
  699. * Return the list of SMTP service extensions advertised by the server.
  700. *
  701. * @return array The list of SMTP service extensions.
  702. * @access public
  703. * @since 1.3
  704. */
  705. function getServiceExtensions()
  706. {
  707. return $this->_esmtp;
  708. }
  709. /**
  710. * Send the MAIL FROM: command.
  711. *
  712. * @param string $sender The sender (reverse path) to set.
  713. * @param string $params String containing additional MAIL parameters,
  714. * such as the NOTIFY flags defined by RFC 1891
  715. * or the VERP protocol.
  716. *
  717. * If $params is an array, only the 'verp' option
  718. * is supported. If 'verp' is true, the XVERP
  719. * parameter is appended to the MAIL command. If
  720. * the 'verp' value is a string, the full
  721. * XVERP=value parameter is appended.
  722. *
  723. * @return mixed Returns a PEAR_Error with an error message on any
  724. * kind of failure, or true on success.
  725. * @access public
  726. * @since 1.0
  727. */
  728. function mailFrom($sender, $params = null)
  729. {
  730. $args = "FROM:<$sender>";
  731. /* Support the deprecated array form of $params. */
  732. if (is_array($params) && isset($params['verp'])) {
  733. /* XVERP */
  734. if ($params['verp'] === true) {
  735. $args .= ' XVERP';
  736. /* XVERP=something */
  737. } elseif (trim($params['verp'])) {
  738. $args .= ' XVERP=' . $params['verp'];
  739. }
  740. } elseif (is_string($params)) {
  741. $args .= ' ' . $params;
  742. }
  743. if (PEAR::isError($error = $this->_put('MAIL', $args))) {
  744. return $error;
  745. }
  746. if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
  747. return $error;
  748. }
  749. return true;
  750. }
  751. /**
  752. * Send the RCPT TO: command.
  753. *
  754. * @param string $recipient The recipient (forward path) to add.
  755. * @param string $params String containing additional RCPT parameters,
  756. * such as the NOTIFY flags defined by RFC 1891.
  757. *
  758. * @return mixed Returns a PEAR_Error with an error message on any
  759. * kind of failure, or true on success.
  760. *
  761. * @access public
  762. * @since 1.0
  763. */
  764. function rcptTo($recipient, $params = null)
  765. {
  766. $args = "TO:<$recipient>";
  767. if (is_string($params)) {
  768. $args .= ' ' . $params;
  769. }
  770. if (PEAR::isError($error = $this->_put('RCPT', $args))) {
  771. return $error;
  772. }
  773. if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) {
  774. return $error;
  775. }
  776. return true;
  777. }
  778. /**
  779. * Quote the data so that it meets SMTP standards.
  780. *
  781. * This is provided as a separate public function to facilitate
  782. * easier overloading for the cases where it is desirable to
  783. * customize the quoting behavior.
  784. *
  785. * @param string $data The message text to quote. The string must be passed
  786. * by reference, and the text will be modified in place.
  787. *
  788. * @access public
  789. * @since 1.2
  790. */
  791. function quotedata(&$data)
  792. {
  793. /* Change Unix (\n) and Mac (\r) linefeeds into
  794. * Internet-standard CRLF (\r\n) linefeeds. */
  795. $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
  796. /* Because a single leading period (.) signifies an end to the
  797. * data, legitimate leading periods need to be "doubled"
  798. * (e.g. '..'). */
  799. $data = str_replace("\n.", "\n..", $data);
  800. }
  801. /**
  802. * Send the DATA command.
  803. *
  804. * @param mixed $data The message data, either as a string or an open
  805. * file resource.
  806. * @param string $headers The message headers. If $headers is provided,
  807. * $data is assumed to contain only body data.
  808. *
  809. * @return mixed Returns a PEAR_Error with an error message on any
  810. * kind of failure, or true on success.
  811. * @access public
  812. * @since 1.0
  813. */
  814. function data($data, $headers = null)
  815. {
  816. /* Verify that $data is a supported type. */
  817. if (!is_string($data) && !is_resource($data)) {
  818. return PEAR::raiseError('Expected a string or file resource');
  819. }
  820. /* RFC 1870, section 3, subsection 3 states "a value of zero
  821. * indicates that no fixed maximum message size is in force".
  822. * Furthermore, it says that if "the parameter is omitted no
  823. * information is conveyed about the server's fixed maximum
  824. * message size". */
  825. if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
  826. /* Start by considering the size of the optional headers string.
  827. * We also account for the addition 4 character "\r\n\r\n"
  828. * separator sequence. */
  829. $size = (is_null($headers)) ? 0 : strlen($headers) + 4;
  830. if (is_resource($data)) {
  831. $stat = fstat($data);
  832. if ($stat === false) {
  833. return PEAR::raiseError('Failed to get file size');
  834. }
  835. $size += $stat['size'];
  836. } else {
  837. $size += strlen($data);
  838. }
  839. if ($size >= $this->_esmtp['SIZE']) {
  840. $this->disconnect();
  841. return PEAR::raiseError('Message size exceeds server limit');
  842. }
  843. }
  844. /* Initiate the DATA command. */
  845. if (PEAR::isError($error = $this->_put('DATA'))) {
  846. return $error;
  847. }
  848. if (PEAR::isError($error = $this->_parseResponse(354))) {
  849. return $error;
  850. }
  851. /* If we have a separate headers string, send it first. */
  852. if (!is_null($headers)) {
  853. $this->quotedata($headers);
  854. if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) {
  855. return $result;
  856. }
  857. }
  858. /* Now we can send the message body data. */
  859. if (is_resource($data)) {
  860. /* Stream the contents of the file resource out over our socket
  861. * connection, line by line. Each line must be run through the
  862. * quoting routine. */
  863. while ($line = fgets($data, 1024)) {
  864. $this->quotedata($line);
  865. if (PEAR::isError($result = $this->_send($line))) {
  866. return $result;
  867. }
  868. }
  869. /* Finally, send the DATA terminator sequence. */
  870. if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) {
  871. return $result;
  872. }
  873. } else {
  874. /* Just send the entire quoted string followed by the DATA
  875. * terminator. */
  876. $this->quotedata($data);
  877. if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) {
  878. return $result;
  879. }
  880. }
  881. /* Verify that the data was successfully received by the server. */
  882. if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
  883. return $error;
  884. }
  885. return true;
  886. }
  887. /**
  888. * Send the SEND FROM: command.
  889. *
  890. * @param string The reverse path to send.
  891. *
  892. * @return mixed Returns a PEAR_Error with an error message on any
  893. * kind of failure, or true on success.
  894. * @access public
  895. * @since 1.2.6
  896. */
  897. function sendFrom($path)
  898. {
  899. if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
  900. return $error;
  901. }
  902. if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
  903. return $error;
  904. }
  905. return true;
  906. }
  907. /**
  908. * Backwards-compatibility wrapper for sendFrom().
  909. *
  910. * @param string The reverse path to send.
  911. *
  912. * @return mixed Returns a PEAR_Error with an error message on any
  913. * kind of failure, or true on success.
  914. *
  915. * @access public
  916. * @since 1.0
  917. * @deprecated 1.2.6
  918. */
  919. function send_from($path)
  920. {
  921. return sendFrom($path);
  922. }
  923. /**
  924. * Send the SOML FROM: command.
  925. *
  926. * @param string The reverse path to send.
  927. *
  928. * @return mixed Returns a PEAR_Error with an error message on any
  929. * kind of failure, or true on success.
  930. * @access public
  931. * @since 1.2.6
  932. */
  933. function somlFrom($path)
  934. {
  935. if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
  936. return $error;
  937. }
  938. if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
  939. return $error;
  940. }
  941. return true;
  942. }
  943. /**
  944. * Backwards-compatibility wrapper for somlFrom().
  945. *
  946. * @param string The reverse path to send.
  947. *
  948. * @return mixed Returns a PEAR_Error with an error message on any
  949. * kind of failure, or true on success.
  950. *
  951. * @access public
  952. * @since 1.0
  953. * @deprecated 1.2.6
  954. */
  955. function soml_from($path)
  956. {
  957. return somlFrom($path);
  958. }
  959. /**
  960. * Send the SAML FROM: command.
  961. *
  962. * @param string The reverse path to send.
  963. *
  964. * @return mixed Returns a PEAR_Error with an error message on any
  965. * kind of failure, or true on success.
  966. * @access public
  967. * @since 1.2.6
  968. */
  969. function samlFrom($path)
  970. {
  971. if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
  972. return $error;
  973. }
  974. if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
  975. return $error;
  976. }
  977. return true;
  978. }
  979. /**
  980. * Backwards-compatibility wrapper for samlFrom().
  981. *
  982. * @param string The reverse path to send.
  983. *
  984. * @return mixed Returns a PEAR_Error with an error message on any
  985. * kind of failure, or true on success.
  986. *
  987. * @access public
  988. * @since 1.0
  989. * @deprecated 1.2.6
  990. */
  991. function saml_from($path)
  992. {
  993. return samlFrom($path);
  994. }
  995. /**
  996. * Send the RSET command.
  997. *
  998. * @return mixed Returns a PEAR_Error with an error message on any
  999. * kind of failure, or true on success.
  1000. * @access public
  1001. * @since 1.0
  1002. */
  1003. function rset()
  1004. {
  1005. if (PEAR::isError($error = $this->_put('RSET'))) {
  1006. return $error;
  1007. }
  1008. if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
  1009. return $error;
  1010. }
  1011. return true;
  1012. }
  1013. /**
  1014. * Send the VRFY command.
  1015. *
  1016. * @param string The string to verify
  1017. *
  1018. * @return mixed Returns a PEAR_Error with an error message on any
  1019. * kind of failure, or true on success.
  1020. * @access public
  1021. * @since 1.0
  1022. */
  1023. function vrfy($string)
  1024. {
  1025. /* Note: 251 is also a valid response code */
  1026. if (PEAR::isError($error = $this->_put('VRFY', $string))) {
  1027. return $error;
  1028. }
  1029. if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
  1030. return $error;
  1031. }
  1032. return true;
  1033. }
  1034. /**
  1035. * Send the NOOP command.
  1036. *
  1037. * @return mixed Returns a PEAR_Error with an error message on any
  1038. * kind of failure, or true on success.
  1039. * @access public
  1040. * @since 1.0
  1041. */
  1042. function noop()
  1043. {
  1044. if (PEAR::isError($error = $this->_put('NOOP'))) {
  1045. return $error;
  1046. }
  1047. if (PEAR::isError($error = $this->_parseResponse(250))) {
  1048. return $error;
  1049. }
  1050. return true;
  1051. }
  1052. /**
  1053. * Backwards-compatibility method. identifySender()'s functionality is
  1054. * now handled internally.
  1055. *
  1056. * @return boolean This method always return true.
  1057. *
  1058. * @access public
  1059. * @since 1.0
  1060. */
  1061. function identifySender()
  1062. {
  1063. return true;
  1064. }
  1065. }