Dashboard sipadu mbip
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.

class.smtp.php 30KB

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