class.smtp.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. <?php
  2. /**
  3. * PHPMailer RFC821 SMTP email transport class.
  4. * PHP Version 5
  5. * @package PHPMailer
  6. * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
  7. * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  8. * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
  9. * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  10. * @author Brent R. Matzelle (original founder)
  11. * @copyright 2014 Marcus Bointon
  12. * @copyright 2010 - 2012 Jim Jagielski
  13. * @copyright 2004 - 2009 Andy Prevost
  14. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  15. * @note This program is distributed in the hope that it will be useful - WITHOUT
  16. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  17. * FITNESS FOR A PARTICULAR PURPOSE.
  18. */
  19. /**
  20. * PHPMailer RFC821 SMTP email transport class.
  21. * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
  22. * @package PHPMailer
  23. * @author Chris Ryan <unknown@example.com>
  24. * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
  25. */
  26. class SMTP
  27. {
  28. /**
  29. * The PHPMailer SMTP version number.
  30. * @type string
  31. */
  32. const VERSION = '5.2.8';
  33. /**
  34. * SMTP line break constant.
  35. * @type string
  36. */
  37. const CRLF = "\r\n";
  38. /**
  39. * The SMTP port to use if one is not specified.
  40. * @type integer
  41. */
  42. const DEFAULT_SMTP_PORT = 25;
  43. /**
  44. * The maximum line length allowed by RFC 2822 section 2.1.1
  45. * @type integer
  46. */
  47. const MAX_LINE_LENGTH = 998;
  48. /**
  49. * The PHPMailer SMTP Version number.
  50. * @type string
  51. * @deprecated Use the `VERSION` constant instead
  52. * @see SMTP::VERSION
  53. */
  54. public $Version = '5.2.8';
  55. /**
  56. * SMTP server port number.
  57. * @type integer
  58. * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
  59. * @see SMTP::DEFAULT_SMTP_PORT
  60. */
  61. public $SMTP_PORT = 25;
  62. /**
  63. * SMTP reply line ending.
  64. * @type string
  65. * @deprecated Use the `CRLF` constant instead
  66. * @see SMTP::CRLF
  67. */
  68. public $CRLF = "\r\n";
  69. /**
  70. * Debug output level.
  71. * Options:
  72. * * `0` No output
  73. * * `1` Commands
  74. * * `2` Data and commands
  75. * * `3` As 2 plus connection status
  76. * * `4` Low-level data output
  77. * @type integer
  78. */
  79. public $do_debug = 0;
  80. /**
  81. * How to handle debug output.
  82. * Options:
  83. * * `echo` Output plain-text as-is, appropriate for CLI
  84. * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
  85. * * `error_log` Output to error log as configured in php.ini
  86. *
  87. * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
  88. * <code>
  89. * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
  90. * </code>
  91. * @type string|callable
  92. */
  93. public $Debugoutput = 'echo';
  94. /**
  95. * Whether to use VERP.
  96. * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
  97. * @link http://www.postfix.org/VERP_README.html Info on VERP
  98. * @type boolean
  99. */
  100. public $do_verp = false;
  101. /**
  102. * The timeout value for connection, in seconds.
  103. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
  104. * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
  105. * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
  106. * @type integer
  107. */
  108. public $Timeout = 300;
  109. /**
  110. * The SMTP timelimit value for reads, in seconds.
  111. * @type integer
  112. */
  113. public $Timelimit = 30;
  114. /**
  115. * The socket for the server connection.
  116. * @type resource
  117. */
  118. protected $smtp_conn;
  119. /**
  120. * Error message, if any, for the last call.
  121. * @type array
  122. */
  123. protected $error = array();
  124. /**
  125. * The reply the server sent to us for HELO.
  126. * If null, no HELO string has yet been received.
  127. * @type string|null
  128. */
  129. protected $helo_rply = null;
  130. /**
  131. * The most recent reply received from the server.
  132. * @type string
  133. */
  134. protected $last_reply = '';
  135. /**
  136. * Output debugging info via a user-selected method.
  137. * @see SMTP::$Debugoutput
  138. * @see SMTP::$do_debug
  139. * @param string $str Debug string to output
  140. * @return void
  141. */
  142. protected function edebug($str)
  143. {
  144. if (is_callable($this->Debugoutput)) {
  145. call_user_func($this->Debugoutput, $str, $this->do_debug);
  146. return;
  147. }
  148. switch ($this->Debugoutput) {
  149. case 'error_log':
  150. //Don't output, just log
  151. error_log($str);
  152. break;
  153. case 'html':
  154. //Cleans up output a bit for a better looking, HTML-safe output
  155. echo htmlentities(
  156. preg_replace('/[\r\n]+/', '', $str),
  157. ENT_QUOTES,
  158. 'UTF-8'
  159. )
  160. . "<br>\n";
  161. break;
  162. case 'echo':
  163. default:
  164. //Normalize line breaks
  165. $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
  166. echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
  167. "\n",
  168. "\n \t ",
  169. trim($str)
  170. )."\n";
  171. }
  172. }
  173. /**
  174. * Connect to an SMTP server.
  175. * @param string $host SMTP server IP or host name
  176. * @param integer $port The port number to connect to
  177. * @param integer $timeout How long to wait for the connection to open
  178. * @param array $options An array of options for stream_context_create()
  179. * @access public
  180. * @return boolean
  181. */
  182. public function connect($host, $port = null, $timeout = 30, $options = array())
  183. {
  184. static $streamok;
  185. //This is enabled by default since 5.0.0 but some providers disable it
  186. //Check this once and cache the result
  187. if (is_null($streamok)) {
  188. $streamok = function_exists('stream_socket_client');
  189. }
  190. // Clear errors to avoid confusion
  191. $this->error = array();
  192. // Make sure we are __not__ connected
  193. if ($this->connected()) {
  194. // Already connected, generate error
  195. $this->error = array('error' => 'Already connected to a server');
  196. return false;
  197. }
  198. if (empty($port)) {
  199. $port = self::DEFAULT_SMTP_PORT;
  200. }
  201. // Connect to the SMTP server
  202. if ($this->do_debug >= 3) {
  203. $this->edebug("Connection: opening to $host:$port, t=$timeout, opt=".var_export($options, true));
  204. }
  205. $errno = 0;
  206. $errstr = '';
  207. if ($streamok) {
  208. $socket_context = stream_context_create($options);
  209. //Suppress errors; connection failures are handled at a higher level
  210. $this->smtp_conn = @stream_socket_client(
  211. $host . ":" . $port,
  212. $errno,
  213. $errstr,
  214. $timeout,
  215. STREAM_CLIENT_CONNECT,
  216. $socket_context
  217. );
  218. } else {
  219. //Fall back to fsockopen which should work in more places, but is missing some features
  220. if ($this->do_debug >= 3) {
  221. $this->edebug("Connection: stream_socket_client not available, falling back to fsockopen");
  222. }
  223. $this->smtp_conn = fsockopen(
  224. $host,
  225. $port,
  226. $errno,
  227. $errstr,
  228. $timeout
  229. );
  230. }
  231. // Verify we connected properly
  232. if (!is_resource($this->smtp_conn)) {
  233. $this->error = array(
  234. 'error' => 'Failed to connect to server',
  235. 'errno' => $errno,
  236. 'errstr' => $errstr
  237. );
  238. if ($this->do_debug >= 1) {
  239. $this->edebug(
  240. 'SMTP ERROR: ' . $this->error['error']
  241. . ": $errstr ($errno)"
  242. );
  243. }
  244. return false;
  245. }
  246. if ($this->do_debug >= 3) {
  247. $this->edebug('Connection: opened');
  248. }
  249. // SMTP server can take longer to respond, give longer timeout for first read
  250. // Windows does not have support for this timeout function
  251. if (substr(PHP_OS, 0, 3) != 'WIN') {
  252. $max = ini_get('max_execution_time');
  253. if ($max != 0 && $timeout > $max) { // Don't bother if unlimited
  254. @set_time_limit($timeout);
  255. }
  256. stream_set_timeout($this->smtp_conn, $timeout, 0);
  257. }
  258. // Get any announcement
  259. $announce = $this->get_lines();
  260. if ($this->do_debug >= 2) {
  261. $this->edebug('SERVER -> CLIENT: ' . $announce);
  262. }
  263. return true;
  264. }
  265. /**
  266. * Initiate a TLS (encrypted) session.
  267. * @access public
  268. * @return boolean
  269. */
  270. public function startTLS()
  271. {
  272. if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
  273. return false;
  274. }
  275. // Begin encrypted connection
  276. if (!stream_socket_enable_crypto(
  277. $this->smtp_conn,
  278. true,
  279. STREAM_CRYPTO_METHOD_TLS_CLIENT
  280. )) {
  281. return false;
  282. }
  283. return true;
  284. }
  285. /**
  286. * Perform SMTP authentication.
  287. * Must be run after hello().
  288. * @see hello()
  289. * @param string $username The user name
  290. * @param string $password The password
  291. * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5)
  292. * @param string $realm The auth realm for NTLM
  293. * @param string $workstation The auth workstation for NTLM
  294. * @access public
  295. * @return boolean True if successfully authenticated.
  296. */
  297. public function authenticate(
  298. $username,
  299. $password,
  300. $authtype = 'LOGIN',
  301. $realm = '',
  302. $workstation = ''
  303. ) {
  304. if (empty($authtype)) {
  305. $authtype = 'LOGIN';
  306. }
  307. switch ($authtype) {
  308. case 'PLAIN':
  309. // Start authentication
  310. if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
  311. return false;
  312. }
  313. // Send encoded username and password
  314. if (!$this->sendCommand(
  315. 'User & Password',
  316. base64_encode("\0" . $username . "\0" . $password),
  317. 235
  318. )
  319. ) {
  320. return false;
  321. }
  322. break;
  323. case 'LOGIN':
  324. // Start authentication
  325. if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
  326. return false;
  327. }
  328. if (!$this->sendCommand("Username", base64_encode($username), 334)) {
  329. return false;
  330. }
  331. if (!$this->sendCommand("Password", base64_encode($password), 235)) {
  332. return false;
  333. }
  334. break;
  335. case 'NTLM':
  336. /*
  337. * ntlm_sasl_client.php
  338. * Bundled with Permission
  339. *
  340. * How to telnet in windows:
  341. * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
  342. * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
  343. */
  344. require_once 'extras/ntlm_sasl_client.php';
  345. $temp = new stdClass();
  346. $ntlm_client = new ntlm_sasl_client_class;
  347. //Check that functions are available
  348. if (!$ntlm_client->Initialize($temp)) {
  349. $this->error = array('error' => $temp->error);
  350. if ($this->do_debug >= 1) {
  351. $this->edebug(
  352. 'You need to enable some modules in your php.ini file: '
  353. . $this->error['error']
  354. );
  355. }
  356. return false;
  357. }
  358. //msg1
  359. $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1
  360. if (!$this->sendCommand(
  361. 'AUTH NTLM',
  362. 'AUTH NTLM ' . base64_encode($msg1),
  363. 334
  364. )
  365. ) {
  366. return false;
  367. }
  368. //Though 0 based, there is a white space after the 3 digit number
  369. //msg2
  370. $challenge = substr($this->last_reply, 3);
  371. $challenge = base64_decode($challenge);
  372. $ntlm_res = $ntlm_client->NTLMResponse(
  373. substr($challenge, 24, 8),
  374. $password
  375. );
  376. //msg3
  377. $msg3 = $ntlm_client->TypeMsg3(
  378. $ntlm_res,
  379. $username,
  380. $realm,
  381. $workstation
  382. );
  383. // send encoded username
  384. return $this->sendCommand('Username', base64_encode($msg3), 235);
  385. case 'CRAM-MD5':
  386. // Start authentication
  387. if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
  388. return false;
  389. }
  390. // Get the challenge
  391. $challenge = base64_decode(substr($this->last_reply, 4));
  392. // Build the response
  393. $response = $username . ' ' . $this->hmac($challenge, $password);
  394. // send encoded credentials
  395. return $this->sendCommand('Username', base64_encode($response), 235);
  396. }
  397. return true;
  398. }
  399. /**
  400. * Calculate an MD5 HMAC hash.
  401. * Works like hash_hmac('md5', $data, $key)
  402. * in case that function is not available
  403. * @param string $data The data to hash
  404. * @param string $key The key to hash with
  405. * @access protected
  406. * @return string
  407. */
  408. protected function hmac($data, $key)
  409. {
  410. if (function_exists('hash_hmac')) {
  411. return hash_hmac('md5', $data, $key);
  412. }
  413. // The following borrowed from
  414. // http://php.net/manual/en/function.mhash.php#27225
  415. // RFC 2104 HMAC implementation for php.
  416. // Creates an md5 HMAC.
  417. // Eliminates the need to install mhash to compute a HMAC
  418. // Hacked by Lance Rushing
  419. $bytelen = 64; // byte length for md5
  420. if (strlen($key) > $bytelen) {
  421. $key = pack('H*', md5($key));
  422. }
  423. $key = str_pad($key, $bytelen, chr(0x00));
  424. $ipad = str_pad('', $bytelen, chr(0x36));
  425. $opad = str_pad('', $bytelen, chr(0x5c));
  426. $k_ipad = $key ^ $ipad;
  427. $k_opad = $key ^ $opad;
  428. return md5($k_opad . pack('H*', md5($k_ipad . $data)));
  429. }
  430. /**
  431. * Check connection state.
  432. * @access public
  433. * @return boolean True if connected.
  434. */
  435. public function connected()
  436. {
  437. if (is_resource($this->smtp_conn)) {
  438. $sock_status = stream_get_meta_data($this->smtp_conn);
  439. if ($sock_status['eof']) {
  440. // the socket is valid but we are not connected
  441. if ($this->do_debug >= 1) {
  442. $this->edebug(
  443. 'SMTP NOTICE: EOF caught while checking if connected'
  444. );
  445. }
  446. $this->close();
  447. return false;
  448. }
  449. return true; // everything looks good
  450. }
  451. return false;
  452. }
  453. /**
  454. * Close the socket and clean up the state of the class.
  455. * Don't use this function without first trying to use QUIT.
  456. * @see quit()
  457. * @access public
  458. * @return void
  459. */
  460. public function close()
  461. {
  462. $this->error = array();
  463. $this->helo_rply = null;
  464. if (is_resource($this->smtp_conn)) {
  465. // close the connection and cleanup
  466. fclose($this->smtp_conn);
  467. if ($this->do_debug >= 3) {
  468. $this->edebug('Connection: closed');
  469. }
  470. }
  471. }
  472. /**
  473. * Send an SMTP DATA command.
  474. * Issues a data command and sends the msg_data to the server,
  475. * finializing the mail transaction. $msg_data is the message
  476. * that is to be send with the headers. Each header needs to be
  477. * on a single line followed by a <CRLF> with the message headers
  478. * and the message body being separated by and additional <CRLF>.
  479. * Implements rfc 821: DATA <CRLF>
  480. * @param string $msg_data Message data to send
  481. * @access public
  482. * @return boolean
  483. */
  484. public function data($msg_data)
  485. {
  486. if (!$this->sendCommand('DATA', 'DATA', 354)) {
  487. return false;
  488. }
  489. /* The server is ready to accept data!
  490. * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
  491. * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
  492. * smaller lines to fit within the limit.
  493. * We will also look for lines that start with a '.' and prepend an additional '.'.
  494. * NOTE: this does not count towards line-length limit.
  495. */
  496. // Normalize line breaks before exploding
  497. $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
  498. /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
  499. * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
  500. * process all lines before a blank line as headers.
  501. */
  502. $field = substr($lines[0], 0, strpos($lines[0], ':'));
  503. $in_headers = false;
  504. if (!empty($field) && strpos($field, ' ') === false) {
  505. $in_headers = true;
  506. }
  507. foreach ($lines as $line) {
  508. $lines_out = array();
  509. if ($in_headers and $line == '') {
  510. $in_headers = false;
  511. }
  512. // ok we need to break this line up into several smaller lines
  513. //This is a small micro-optimisation: isset($str[$len]) is equivalent to (strlen($str) > $len)
  514. while (isset($line[self::MAX_LINE_LENGTH])) {
  515. //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
  516. //so as to avoid breaking in the middle of a word
  517. $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
  518. if (!$pos) { //Deliberately matches both false and 0
  519. //No nice break found, add a hard break
  520. $pos = self::MAX_LINE_LENGTH - 1;
  521. $lines_out[] = substr($line, 0, $pos);
  522. $line = substr($line, $pos);
  523. } else {
  524. //Break at the found point
  525. $lines_out[] = substr($line, 0, $pos);
  526. //Move along by the amount we dealt with
  527. $line = substr($line, $pos + 1);
  528. }
  529. /* If processing headers add a LWSP-char to the front of new line
  530. * RFC822 section 3.1.1
  531. */
  532. if ($in_headers) {
  533. $line = "\t" . $line;
  534. }
  535. }
  536. $lines_out[] = $line;
  537. // Send the lines to the server
  538. foreach ($lines_out as $line_out) {
  539. //RFC2821 section 4.5.2
  540. if (!empty($line_out) and $line_out[0] == '.') {
  541. $line_out = '.' . $line_out;
  542. }
  543. $this->client_send($line_out . self::CRLF);
  544. }
  545. }
  546. // Message data has been sent, complete the command
  547. return $this->sendCommand('DATA END', '.', 250);
  548. }
  549. /**
  550. * Send an SMTP HELO or EHLO command.
  551. * Used to identify the sending server to the receiving server.
  552. * This makes sure that client and server are in a known state.
  553. * Implements RFC 821: HELO <SP> <domain> <CRLF>
  554. * and RFC 2821 EHLO.
  555. * @param string $host The host name or IP to connect to
  556. * @access public
  557. * @return boolean
  558. */
  559. public function hello($host = '')
  560. {
  561. // Try extended hello first (RFC 2821)
  562. return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
  563. }
  564. /**
  565. * Send an SMTP HELO or EHLO command.
  566. * Low-level implementation used by hello()
  567. * @see hello()
  568. * @param string $hello The HELO string
  569. * @param string $host The hostname to say we are
  570. * @access protected
  571. * @return boolean
  572. */
  573. protected function sendHello($hello, $host)
  574. {
  575. $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
  576. $this->helo_rply = $this->last_reply;
  577. return $noerror;
  578. }
  579. /**
  580. * Send an SMTP MAIL command.
  581. * Starts a mail transaction from the email address specified in
  582. * $from. Returns true if successful or false otherwise. If True
  583. * the mail transaction is started and then one or more recipient
  584. * commands may be called followed by a data command.
  585. * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
  586. * @param string $from Source address of this message
  587. * @access public
  588. * @return boolean
  589. */
  590. public function mail($from)
  591. {
  592. $useVerp = ($this->do_verp ? ' XVERP' : '');
  593. return $this->sendCommand(
  594. 'MAIL FROM',
  595. 'MAIL FROM:<' . $from . '>' . $useVerp,
  596. 250
  597. );
  598. }
  599. /**
  600. * Send an SMTP QUIT command.
  601. * Closes the socket if there is no error or the $close_on_error argument is true.
  602. * Implements from rfc 821: QUIT <CRLF>
  603. * @param boolean $close_on_error Should the connection close if an error occurs?
  604. * @access public
  605. * @return boolean
  606. */
  607. public function quit($close_on_error = true)
  608. {
  609. $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
  610. $err = $this->error; //Save any error
  611. if ($noerror or $close_on_error) {
  612. $this->close();
  613. $this->error = $err; //Restore any error from the quit command
  614. }
  615. return $noerror;
  616. }
  617. /**
  618. * Send an SMTP RCPT command.
  619. * Sets the TO argument to $toaddr.
  620. * Returns true if the recipient was accepted false if it was rejected.
  621. * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
  622. * @param string $toaddr The address the message is being sent to
  623. * @access public
  624. * @return boolean
  625. */
  626. public function recipient($toaddr)
  627. {
  628. return $this->sendCommand(
  629. 'RCPT TO',
  630. 'RCPT TO:<' . $toaddr . '>',
  631. array(250, 251)
  632. );
  633. }
  634. /**
  635. * Send an SMTP RSET command.
  636. * Abort any transaction that is currently in progress.
  637. * Implements rfc 821: RSET <CRLF>
  638. * @access public
  639. * @return boolean True on success.
  640. */
  641. public function reset()
  642. {
  643. return $this->sendCommand('RSET', 'RSET', 250);
  644. }
  645. /**
  646. * Send a command to an SMTP server and check its return code.
  647. * @param string $command The command name - not sent to the server
  648. * @param string $commandstring The actual command to send
  649. * @param integer|array $expect One or more expected integer success codes
  650. * @access protected
  651. * @return boolean True on success.
  652. */
  653. protected function sendCommand($command, $commandstring, $expect)
  654. {
  655. if (!$this->connected()) {
  656. $this->error = array(
  657. 'error' => "Called $command without being connected"
  658. );
  659. return false;
  660. }
  661. $this->client_send($commandstring . self::CRLF);
  662. $this->last_reply = $this->get_lines();
  663. $code = substr($this->last_reply, 0, 3);
  664. if ($this->do_debug >= 2) {
  665. $this->edebug('SERVER -> CLIENT: ' . $this->last_reply);
  666. }
  667. if (!in_array($code, (array)$expect)) {
  668. $this->error = array(
  669. 'error' => "$command command failed",
  670. 'smtp_code' => $code,
  671. 'detail' => substr($this->last_reply, 4)
  672. );
  673. if ($this->do_debug >= 1) {
  674. $this->edebug(
  675. 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply
  676. );
  677. }
  678. return false;
  679. }
  680. $this->error = array();
  681. return true;
  682. }
  683. /**
  684. * Send an SMTP SAML command.
  685. * Starts a mail transaction from the email address specified in $from.
  686. * Returns true if successful or false otherwise. If True
  687. * the mail transaction is started and then one or more recipient
  688. * commands may be called followed by a data command. This command
  689. * will send the message to the users terminal if they are logged
  690. * in and send them an email.
  691. * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
  692. * @param string $from The address the message is from
  693. * @access public
  694. * @return boolean
  695. */
  696. public function sendAndMail($from)
  697. {
  698. return $this->sendCommand('SAML', "SAML FROM:$from", 250);
  699. }
  700. /**
  701. * Send an SMTP VRFY command.
  702. * @param string $name The name to verify
  703. * @access public
  704. * @return boolean
  705. */
  706. public function verify($name)
  707. {
  708. return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
  709. }
  710. /**
  711. * Send an SMTP NOOP command.
  712. * Used to keep keep-alives alive, doesn't actually do anything
  713. * @access public
  714. * @return boolean
  715. */
  716. public function noop()
  717. {
  718. return $this->sendCommand('NOOP', 'NOOP', 250);
  719. }
  720. /**
  721. * Send an SMTP TURN command.
  722. * This is an optional command for SMTP that this class does not support.
  723. * This method is here to make the RFC821 Definition complete for this class
  724. * and _may_ be implemented in future
  725. * Implements from rfc 821: TURN <CRLF>
  726. * @access public
  727. * @return boolean
  728. */
  729. public function turn()
  730. {
  731. $this->error = array(
  732. 'error' => 'The SMTP TURN command is not implemented'
  733. );
  734. if ($this->do_debug >= 1) {
  735. $this->edebug('SMTP NOTICE: ' . $this->error['error']);
  736. }
  737. return false;
  738. }
  739. /**
  740. * Send raw data to the server.
  741. * @param string $data The data to send
  742. * @access public
  743. * @return integer|boolean The number of bytes sent to the server or false on error
  744. */
  745. public function client_send($data)
  746. {
  747. if ($this->do_debug >= 1) {
  748. $this->edebug("CLIENT -> SERVER: $data");
  749. }
  750. return fwrite($this->smtp_conn, $data);
  751. }
  752. /**
  753. * Get the latest error.
  754. * @access public
  755. * @return array
  756. */
  757. public function getError()
  758. {
  759. return $this->error;
  760. }
  761. /**
  762. * Get the last reply from the server.
  763. * @access public
  764. * @return string
  765. */
  766. public function getLastReply()
  767. {
  768. return $this->last_reply;
  769. }
  770. /**
  771. * Read the SMTP server's response.
  772. * Either before eof or socket timeout occurs on the operation.
  773. * With SMTP we can tell if we have more lines to read if the
  774. * 4th character is '-' symbol. If it is a space then we don't
  775. * need to read anything else.
  776. * @access protected
  777. * @return string
  778. */
  779. protected function get_lines()
  780. {
  781. // If the connection is bad, give up straight away
  782. if (!is_resource($this->smtp_conn)) {
  783. return '';
  784. }
  785. $data = '';
  786. $endtime = 0;
  787. stream_set_timeout($this->smtp_conn, $this->Timeout);
  788. if ($this->Timelimit > 0) {
  789. $endtime = time() + $this->Timelimit;
  790. }
  791. while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
  792. $str = @fgets($this->smtp_conn, 515);
  793. if ($this->do_debug >= 4) {
  794. $this->edebug("SMTP -> get_lines(): \$data was \"$data\"");
  795. $this->edebug("SMTP -> get_lines(): \$str is \"$str\"");
  796. }
  797. $data .= $str;
  798. if ($this->do_debug >= 4) {
  799. $this->edebug("SMTP -> get_lines(): \$data is \"$data\"");
  800. }
  801. // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
  802. if ((isset($str[3]) and $str[3] == ' ')) {
  803. break;
  804. }
  805. // Timed-out? Log and break
  806. $info = stream_get_meta_data($this->smtp_conn);
  807. if ($info['timed_out']) {
  808. if ($this->do_debug >= 4) {
  809. $this->edebug(
  810. 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)'
  811. );
  812. }
  813. break;
  814. }
  815. // Now check if reads took too long
  816. if ($endtime and time() > $endtime) {
  817. if ($this->do_debug >= 4) {
  818. $this->edebug(
  819. 'SMTP -> get_lines(): timelimit reached ('.
  820. $this->Timelimit . ' sec)'
  821. );
  822. }
  823. break;
  824. }
  825. }
  826. return $data;
  827. }
  828. /**
  829. * Enable or disable VERP address generation.
  830. * @param boolean $enabled
  831. */
  832. public function setVerp($enabled = false)
  833. {
  834. $this->do_verp = $enabled;
  835. }
  836. /**
  837. * Get VERP address generation mode.
  838. * @return boolean
  839. */
  840. public function getVerp()
  841. {
  842. return $this->do_verp;
  843. }
  844. /**
  845. * Set debug output method.
  846. * @param string $method The function/method to use for debugging output.
  847. */
  848. public function setDebugOutput($method = 'echo')
  849. {
  850. $this->Debugoutput = $method;
  851. }
  852. /**
  853. * Get debug output method.
  854. * @return string
  855. */
  856. public function getDebugOutput()
  857. {
  858. return $this->Debugoutput;
  859. }
  860. /**
  861. * Set debug output level.
  862. * @param integer $level
  863. */
  864. public function setDebugLevel($level = 0)
  865. {
  866. $this->do_debug = $level;
  867. }
  868. /**
  869. * Get debug output level.
  870. * @return integer
  871. */
  872. public function getDebugLevel()
  873. {
  874. return $this->do_debug;
  875. }
  876. /**
  877. * Set SMTP timeout.
  878. * @param integer $timeout
  879. */
  880. public function setTimeout($timeout = 0)
  881. {
  882. $this->Timeout = $timeout;
  883. }
  884. /**
  885. * Get SMTP timeout.
  886. * @return integer
  887. */
  888. public function getTimeout()
  889. {
  890. return $this->Timeout;
  891. }
  892. }