SMTP.php 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  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
  24. * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
  25. */
  26. namespace phpmailer;
  27. class SMTP
  28. {
  29. /**
  30. * The PHPMailer SMTP version number.
  31. * @var string
  32. */
  33. const VERSION = '5.2.28';
  34. /**
  35. * SMTP line break constant.
  36. * @var string
  37. */
  38. const CRLF = "\r\n";
  39. /**
  40. * The SMTP port to use if one is not specified.
  41. * @var integer
  42. */
  43. const DEFAULT_SMTP_PORT = 25;
  44. /**
  45. * The maximum line length allowed by RFC 2822 section 2.1.1
  46. * @var integer
  47. */
  48. const MAX_LINE_LENGTH = 998;
  49. /**
  50. * Debug level for no output
  51. */
  52. const DEBUG_OFF = 0;
  53. /**
  54. * Debug level to show client -> server messages
  55. */
  56. const DEBUG_CLIENT = 1;
  57. /**
  58. * Debug level to show client -> server and server -> client messages
  59. */
  60. const DEBUG_SERVER = 2;
  61. /**
  62. * Debug level to show connection status, client -> server and server -> client messages
  63. */
  64. const DEBUG_CONNECTION = 3;
  65. /**
  66. * Debug level to show all messages
  67. */
  68. const DEBUG_LOWLEVEL = 4;
  69. /**
  70. * The PHPMailer SMTP Version number.
  71. * @var string
  72. * @deprecated Use the `VERSION` constant instead
  73. * @see SMTP::VERSION
  74. */
  75. public $Version = '5.2.28';
  76. /**
  77. * SMTP server port number.
  78. * @var integer
  79. * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
  80. * @see SMTP::DEFAULT_SMTP_PORT
  81. */
  82. public $SMTP_PORT = 25;
  83. /**
  84. * SMTP reply line ending.
  85. * @var string
  86. * @deprecated Use the `CRLF` constant instead
  87. * @see SMTP::CRLF
  88. */
  89. public $CRLF = "\r\n";
  90. /**
  91. * Debug output level.
  92. * Options:
  93. * * self::DEBUG_OFF (`0`) No debug output, default
  94. * * self::DEBUG_CLIENT (`1`) Client commands
  95. * * self::DEBUG_SERVER (`2`) Client commands and server responses
  96. * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
  97. * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
  98. * @var integer
  99. */
  100. public $do_debug = self::DEBUG_OFF;
  101. /**
  102. * How to handle debug output.
  103. * Options:
  104. * * `echo` Output plain-text as-is, appropriate for CLI
  105. * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
  106. * * `error_log` Output to error log as configured in php.ini
  107. *
  108. * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
  109. * <code>
  110. * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
  111. * </code>
  112. * @var string|callable
  113. */
  114. public $Debugoutput = 'echo';
  115. /**
  116. * Whether to use VERP.
  117. * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
  118. * @link http://www.postfix.org/VERP_README.html Info on VERP
  119. * @var boolean
  120. */
  121. public $do_verp = false;
  122. /**
  123. * The timeout value for connection, in seconds.
  124. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
  125. * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
  126. * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
  127. * @var integer
  128. */
  129. public $Timeout = 300;
  130. /**
  131. * How long to wait for commands to complete, in seconds.
  132. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
  133. * @var integer
  134. */
  135. public $Timelimit = 300;
  136. /**
  137. * @var array Patterns to extract an SMTP transaction id from reply to a DATA command.
  138. * The first capture group in each regex will be used as the ID.
  139. */
  140. protected $smtp_transaction_id_patterns = array(
  141. 'exim' => '/[0-9]{3} OK id=(.*)/',
  142. 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
  143. 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
  144. );
  145. /**
  146. * @var string The last transaction ID issued in response to a DATA command,
  147. * if one was detected
  148. */
  149. protected $last_smtp_transaction_id;
  150. /**
  151. * The socket for the server connection.
  152. * @var resource
  153. */
  154. protected $smtp_conn;
  155. /**
  156. * Error information, if any, for the last SMTP command.
  157. * @var array
  158. */
  159. protected $error = array(
  160. 'error' => '',
  161. 'detail' => '',
  162. 'smtp_code' => '',
  163. 'smtp_code_ex' => ''
  164. );
  165. /**
  166. * The reply the server sent to us for HELO.
  167. * If null, no HELO string has yet been received.
  168. * @var string|null
  169. */
  170. protected $helo_rply = null;
  171. /**
  172. * The set of SMTP extensions sent in reply to EHLO command.
  173. * Indexes of the array are extension names.
  174. * Value at index 'HELO' or 'EHLO' (according to command that was sent)
  175. * represents the server name. In case of HELO it is the only element of the array.
  176. * Other values can be boolean TRUE or an array containing extension options.
  177. * If null, no HELO/EHLO string has yet been received.
  178. * @var array|null
  179. */
  180. protected $server_caps = null;
  181. /**
  182. * The most recent reply received from the server.
  183. * @var string
  184. */
  185. protected $last_reply = '';
  186. /**
  187. * Output debugging info via a user-selected method.
  188. * @see SMTP::$Debugoutput
  189. * @see SMTP::$do_debug
  190. * @param string $str Debug string to output
  191. * @param integer $level The debug level of this message; see DEBUG_* constants
  192. * @return void
  193. */
  194. protected function edebug($str, $level = 0)
  195. {
  196. if ($level > $this->do_debug) {
  197. return;
  198. }
  199. //Avoid clash with built-in function names
  200. if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
  201. call_user_func($this->Debugoutput, $str, $level);
  202. return;
  203. }
  204. switch ($this->Debugoutput) {
  205. case 'error_log':
  206. //Don't output, just log
  207. error_log($str);
  208. break;
  209. case 'html':
  210. //Cleans up output a bit for a better looking, HTML-safe output
  211. echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
  212. preg_replace('/[\r\n]+/', '', $str),
  213. ENT_QUOTES,
  214. 'UTF-8'
  215. ) . "<br>\n";
  216. break;
  217. case 'echo':
  218. default:
  219. //Normalize line breaks
  220. $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
  221. echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
  222. "\n",
  223. "\n \t ",
  224. trim($str)
  225. ) . "\n";
  226. }
  227. }
  228. /**
  229. * Connect to an SMTP server.
  230. * @param string $host SMTP server IP or host name
  231. * @param integer $port The port number to connect to
  232. * @param integer $timeout How long to wait for the connection to open
  233. * @param array $options An array of options for stream_context_create()
  234. * @access public
  235. * @return boolean
  236. */
  237. public function connect($host, $port = null, $timeout = 30, $options = array())
  238. {
  239. static $streamok;
  240. //This is enabled by default since 5.0.0 but some providers disable it
  241. //Check this once and cache the result
  242. if (is_null($streamok)) {
  243. $streamok = function_exists('stream_socket_client');
  244. }
  245. // Clear errors to avoid confusion
  246. $this->setError('');
  247. // Make sure we are __not__ connected
  248. if ($this->connected()) {
  249. // Already connected, generate error
  250. $this->setError('Already connected to a server');
  251. return false;
  252. }
  253. if (empty($port)) {
  254. $port = self::DEFAULT_SMTP_PORT;
  255. }
  256. // Connect to the SMTP server
  257. $this->edebug(
  258. "Connection: opening to $host:$port, timeout=$timeout, options=" .
  259. var_export($options, true),
  260. self::DEBUG_CONNECTION
  261. );
  262. $errno = 0;
  263. $errstr = '';
  264. if ($streamok) {
  265. $socket_context = stream_context_create($options);
  266. set_error_handler(array($this, 'errorHandler'));
  267. $this->smtp_conn = stream_socket_client(
  268. $host . ":" . $port,
  269. $errno,
  270. $errstr,
  271. $timeout,
  272. STREAM_CLIENT_CONNECT,
  273. $socket_context
  274. );
  275. restore_error_handler();
  276. } else {
  277. //Fall back to fsockopen which should work in more places, but is missing some features
  278. $this->edebug(
  279. "Connection: stream_socket_client not available, falling back to fsockopen",
  280. self::DEBUG_CONNECTION
  281. );
  282. set_error_handler(array($this, 'errorHandler'));
  283. $this->smtp_conn = fsockopen(
  284. $host,
  285. $port,
  286. $errno,
  287. $errstr,
  288. $timeout
  289. );
  290. restore_error_handler();
  291. }
  292. // Verify we connected properly
  293. if (!is_resource($this->smtp_conn)) {
  294. $this->setError(
  295. 'Failed to connect to server',
  296. $errno,
  297. $errstr
  298. );
  299. $this->edebug(
  300. 'SMTP ERROR: ' . $this->error['error']
  301. . ": $errstr ($errno)",
  302. self::DEBUG_CLIENT
  303. );
  304. return false;
  305. }
  306. $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
  307. // SMTP server can take longer to respond, give longer timeout for first read
  308. // Windows does not have support for this timeout function
  309. if (substr(PHP_OS, 0, 3) != 'WIN') {
  310. $max = ini_get('max_execution_time');
  311. // Don't bother if unlimited
  312. if ($max != 0 && $timeout > $max) {
  313. @set_time_limit($timeout);
  314. }
  315. stream_set_timeout($this->smtp_conn, $timeout, 0);
  316. }
  317. // Get any announcement
  318. $announce = $this->get_lines();
  319. $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
  320. return true;
  321. }
  322. /**
  323. * Initiate a TLS (encrypted) session.
  324. * @access public
  325. * @return boolean
  326. */
  327. public function startTLS()
  328. {
  329. if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
  330. return false;
  331. }
  332. //Allow the best TLS version(s) we can
  333. $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
  334. //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
  335. //so add them back in manually if we can
  336. if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
  337. $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
  338. $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
  339. }
  340. // Begin encrypted connection
  341. set_error_handler(array($this, 'errorHandler'));
  342. $crypto_ok = stream_socket_enable_crypto(
  343. $this->smtp_conn,
  344. true,
  345. $crypto_method
  346. );
  347. restore_error_handler();
  348. return $crypto_ok;
  349. }
  350. /**
  351. * Perform SMTP authentication.
  352. * Must be run after hello().
  353. * @see hello()
  354. * @param string $username The user name
  355. * @param string $password The password
  356. * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2)
  357. * @param string $realm The auth realm for NTLM
  358. * @param string $workstation The auth workstation for NTLM
  359. * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
  360. * @return bool True if successfully authenticated.* @access public
  361. */
  362. public function authenticate(
  363. $username,
  364. $password,
  365. $authtype = null,
  366. $realm = '',
  367. $workstation = '',
  368. $OAuth = null
  369. ) {
  370. if (!$this->server_caps) {
  371. $this->setError('Authentication is not allowed before HELO/EHLO');
  372. return false;
  373. }
  374. if (array_key_exists('EHLO', $this->server_caps)) {
  375. // SMTP extensions are available; try to find a proper authentication method
  376. if (!array_key_exists('AUTH', $this->server_caps)) {
  377. $this->setError('Authentication is not allowed at this stage');
  378. // 'at this stage' means that auth may be allowed after the stage changes
  379. // e.g. after STARTTLS
  380. return false;
  381. }
  382. self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
  383. self::edebug(
  384. 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
  385. self::DEBUG_LOWLEVEL
  386. );
  387. if (empty($authtype)) {
  388. foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
  389. if (in_array($method, $this->server_caps['AUTH'])) {
  390. $authtype = $method;
  391. break;
  392. }
  393. }
  394. if (empty($authtype)) {
  395. $this->setError('No supported authentication methods found');
  396. return false;
  397. }
  398. self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
  399. }
  400. if (!in_array($authtype, $this->server_caps['AUTH'])) {
  401. $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
  402. return false;
  403. }
  404. } elseif (empty($authtype)) {
  405. $authtype = 'LOGIN';
  406. }
  407. switch ($authtype) {
  408. case 'PLAIN':
  409. // Start authentication
  410. if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
  411. return false;
  412. }
  413. // Send encoded username and password
  414. if (!$this->sendCommand(
  415. 'User & Password',
  416. base64_encode("\0" . $username . "\0" . $password),
  417. 235
  418. )
  419. ) {
  420. return false;
  421. }
  422. break;
  423. case 'LOGIN':
  424. // Start authentication
  425. if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
  426. return false;
  427. }
  428. if (!$this->sendCommand("Username", base64_encode($username), 334)) {
  429. return false;
  430. }
  431. if (!$this->sendCommand("Password", base64_encode($password), 235)) {
  432. return false;
  433. }
  434. break;
  435. case 'XOAUTH2':
  436. //If the OAuth Instance is not set. Can be a case when PHPMailer is used
  437. //instead of PHPMailerOAuth
  438. if (is_null($OAuth)) {
  439. return false;
  440. }
  441. $oauth = $OAuth->getOauth64();
  442. // Start authentication
  443. if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
  444. return false;
  445. }
  446. break;
  447. case 'NTLM':
  448. /*
  449. * ntlm_sasl_client.php
  450. * Bundled with Permission
  451. *
  452. * How to telnet in windows:
  453. * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
  454. * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
  455. */
  456. require_once 'extras/ntlm_sasl_client.php';
  457. $temp = new stdClass;
  458. $ntlm_client = new ntlm_sasl_client_class;
  459. //Check that functions are available
  460. if (!$ntlm_client->initialize($temp)) {
  461. $this->setError($temp->error);
  462. $this->edebug(
  463. 'You need to enable some modules in your php.ini file: '
  464. . $this->error['error'],
  465. self::DEBUG_CLIENT
  466. );
  467. return false;
  468. }
  469. //msg1
  470. $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1
  471. if (!$this->sendCommand(
  472. 'AUTH NTLM',
  473. 'AUTH NTLM ' . base64_encode($msg1),
  474. 334
  475. )
  476. ) {
  477. return false;
  478. }
  479. //Though 0 based, there is a white space after the 3 digit number
  480. //msg2
  481. $challenge = substr($this->last_reply, 3);
  482. $challenge = base64_decode($challenge);
  483. $ntlm_res = $ntlm_client->NTLMResponse(
  484. substr($challenge, 24, 8),
  485. $password
  486. );
  487. //msg3
  488. $msg3 = $ntlm_client->typeMsg3(
  489. $ntlm_res,
  490. $username,
  491. $realm,
  492. $workstation
  493. );
  494. // send encoded username
  495. return $this->sendCommand('Username', base64_encode($msg3), 235);
  496. case 'CRAM-MD5':
  497. // Start authentication
  498. if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
  499. return false;
  500. }
  501. // Get the challenge
  502. $challenge = base64_decode(substr($this->last_reply, 4));
  503. // Build the response
  504. $response = $username . ' ' . $this->hmac($challenge, $password);
  505. // send encoded credentials
  506. return $this->sendCommand('Username', base64_encode($response), 235);
  507. default:
  508. $this->setError("Authentication method \"$authtype\" is not supported");
  509. return false;
  510. }
  511. return true;
  512. }
  513. /**
  514. * Calculate an MD5 HMAC hash.
  515. * Works like hash_hmac('md5', $data, $key)
  516. * in case that function is not available
  517. * @param string $data The data to hash
  518. * @param string $key The key to hash with
  519. * @access protected
  520. * @return string
  521. */
  522. protected function hmac($data, $key)
  523. {
  524. if (function_exists('hash_hmac')) {
  525. return hash_hmac('md5', $data, $key);
  526. }
  527. // The following borrowed from
  528. // http://php.net/manual/en/function.mhash.php#27225
  529. // RFC 2104 HMAC implementation for php.
  530. // Creates an md5 HMAC.
  531. // Eliminates the need to install mhash to compute a HMAC
  532. // by Lance Rushing
  533. $bytelen = 64; // byte length for md5
  534. if (strlen($key) > $bytelen) {
  535. $key = pack('H*', md5($key));
  536. }
  537. $key = str_pad($key, $bytelen, chr(0x00));
  538. $ipad = str_pad('', $bytelen, chr(0x36));
  539. $opad = str_pad('', $bytelen, chr(0x5c));
  540. $k_ipad = $key ^ $ipad;
  541. $k_opad = $key ^ $opad;
  542. return md5($k_opad . pack('H*', md5($k_ipad . $data)));
  543. }
  544. /**
  545. * Check connection state.
  546. * @access public
  547. * @return boolean True if connected.
  548. */
  549. public function connected()
  550. {
  551. if (is_resource($this->smtp_conn)) {
  552. $sock_status = stream_get_meta_data($this->smtp_conn);
  553. if ($sock_status['eof']) {
  554. // The socket is valid but we are not connected
  555. $this->edebug(
  556. 'SMTP NOTICE: EOF caught while checking if connected',
  557. self::DEBUG_CLIENT
  558. );
  559. $this->close();
  560. return false;
  561. }
  562. return true; // everything looks good
  563. }
  564. return false;
  565. }
  566. /**
  567. * Close the socket and clean up the state of the class.
  568. * Don't use this function without first trying to use QUIT.
  569. * @see quit()
  570. * @access public
  571. * @return void
  572. */
  573. public function close()
  574. {
  575. $this->setError('');
  576. $this->server_caps = null;
  577. $this->helo_rply = null;
  578. if (is_resource($this->smtp_conn)) {
  579. // close the connection and cleanup
  580. fclose($this->smtp_conn);
  581. $this->smtp_conn = null; //Makes for cleaner serialization
  582. $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
  583. }
  584. }
  585. /**
  586. * Send an SMTP DATA command.
  587. * Issues a data command and sends the msg_data to the server,
  588. * finializing the mail transaction. $msg_data is the message
  589. * that is to be send with the headers. Each header needs to be
  590. * on a single line followed by a <CRLF> with the message headers
  591. * and the message body being separated by and additional <CRLF>.
  592. * Implements rfc 821: DATA <CRLF>
  593. * @param string $msg_data Message data to send
  594. * @access public
  595. * @return boolean
  596. */
  597. public function data($msg_data)
  598. {
  599. //This will use the standard timelimit
  600. if (!$this->sendCommand('DATA', 'DATA', 354)) {
  601. return false;
  602. }
  603. /* The server is ready to accept data!
  604. * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
  605. * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
  606. * smaller lines to fit within the limit.
  607. * We will also look for lines that start with a '.' and prepend an additional '.'.
  608. * NOTE: this does not count towards line-length limit.
  609. */
  610. // Normalize line breaks before exploding
  611. $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
  612. /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
  613. * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
  614. * process all lines before a blank line as headers.
  615. */
  616. $field = substr($lines[0], 0, strpos($lines[0], ':'));
  617. $in_headers = false;
  618. if (!empty($field) && strpos($field, ' ') === false) {
  619. $in_headers = true;
  620. }
  621. foreach ($lines as $line) {
  622. $lines_out = array();
  623. if ($in_headers and $line == '') {
  624. $in_headers = false;
  625. }
  626. //Break this line up into several smaller lines if it's too long
  627. //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
  628. while (isset($line[self::MAX_LINE_LENGTH])) {
  629. //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
  630. //so as to avoid breaking in the middle of a word
  631. $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
  632. //Deliberately matches both false and 0
  633. if (!$pos) {
  634. //No nice break found, add a hard break
  635. $pos = self::MAX_LINE_LENGTH - 1;
  636. $lines_out[] = substr($line, 0, $pos);
  637. $line = substr($line, $pos);
  638. } else {
  639. //Break at the found point
  640. $lines_out[] = substr($line, 0, $pos);
  641. //Move along by the amount we dealt with
  642. $line = substr($line, $pos + 1);
  643. }
  644. //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
  645. if ($in_headers) {
  646. $line = "\t" . $line;
  647. }
  648. }
  649. $lines_out[] = $line;
  650. //Send the lines to the server
  651. foreach ($lines_out as $line_out) {
  652. //RFC2821 section 4.5.2
  653. if (!empty($line_out) and $line_out[0] == '.') {
  654. $line_out = '.' . $line_out;
  655. }
  656. $this->client_send($line_out . self::CRLF);
  657. }
  658. }
  659. //Message data has been sent, complete the command
  660. //Increase timelimit for end of DATA command
  661. $savetimelimit = $this->Timelimit;
  662. $this->Timelimit = $this->Timelimit * 2;
  663. $result = $this->sendCommand('DATA END', '.', 250);
  664. $this->recordLastTransactionID();
  665. //Restore timelimit
  666. $this->Timelimit = $savetimelimit;
  667. return $result;
  668. }
  669. /**
  670. * Send an SMTP HELO or EHLO command.
  671. * Used to identify the sending server to the receiving server.
  672. * This makes sure that client and server are in a known state.
  673. * Implements RFC 821: HELO <SP> <domain> <CRLF>
  674. * and RFC 2821 EHLO.
  675. * @param string $host The host name or IP to connect to
  676. * @access public
  677. * @return boolean
  678. */
  679. public function hello($host = '')
  680. {
  681. //Try extended hello first (RFC 2821)
  682. return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
  683. }
  684. /**
  685. * Send an SMTP HELO or EHLO command.
  686. * Low-level implementation used by hello()
  687. * @see hello()
  688. * @param string $hello The HELO string
  689. * @param string $host The hostname to say we are
  690. * @access protected
  691. * @return boolean
  692. */
  693. protected function sendHello($hello, $host)
  694. {
  695. $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
  696. $this->helo_rply = $this->last_reply;
  697. if ($noerror) {
  698. $this->parseHelloFields($hello);
  699. } else {
  700. $this->server_caps = null;
  701. }
  702. return $noerror;
  703. }
  704. /**
  705. * Parse a reply to HELO/EHLO command to discover server extensions.
  706. * In case of HELO, the only parameter that can be discovered is a server name.
  707. * @access protected
  708. * @param string $type - 'HELO' or 'EHLO'
  709. */
  710. protected function parseHelloFields($type)
  711. {
  712. $this->server_caps = array();
  713. $lines = explode("\n", $this->helo_rply);
  714. foreach ($lines as $n => $s) {
  715. //First 4 chars contain response code followed by - or space
  716. $s = trim(substr($s, 4));
  717. if (empty($s)) {
  718. continue;
  719. }
  720. $fields = explode(' ', $s);
  721. if (!empty($fields)) {
  722. if (!$n) {
  723. $name = $type;
  724. $fields = $fields[0];
  725. } else {
  726. $name = array_shift($fields);
  727. switch ($name) {
  728. case 'SIZE':
  729. $fields = ($fields ? $fields[0] : 0);
  730. break;
  731. case 'AUTH':
  732. if (!is_array($fields)) {
  733. $fields = array();
  734. }
  735. break;
  736. default:
  737. $fields = true;
  738. }
  739. }
  740. $this->server_caps[$name] = $fields;
  741. }
  742. }
  743. }
  744. /**
  745. * Send an SMTP MAIL command.
  746. * Starts a mail transaction from the email address specified in
  747. * $from. Returns true if successful or false otherwise. If True
  748. * the mail transaction is started and then one or more recipient
  749. * commands may be called followed by a data command.
  750. * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
  751. * @param string $from Source address of this message
  752. * @access public
  753. * @return boolean
  754. */
  755. public function mail($from)
  756. {
  757. $useVerp = ($this->do_verp ? ' XVERP' : '');
  758. return $this->sendCommand(
  759. 'MAIL FROM',
  760. 'MAIL FROM:<' . $from . '>' . $useVerp,
  761. 250
  762. );
  763. }
  764. /**
  765. * Send an SMTP QUIT command.
  766. * Closes the socket if there is no error or the $close_on_error argument is true.
  767. * Implements from rfc 821: QUIT <CRLF>
  768. * @param boolean $close_on_error Should the connection close if an error occurs?
  769. * @access public
  770. * @return boolean
  771. */
  772. public function quit($close_on_error = true)
  773. {
  774. $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
  775. $err = $this->error; //Save any error
  776. if ($noerror or $close_on_error) {
  777. $this->close();
  778. $this->error = $err; //Restore any error from the quit command
  779. }
  780. return $noerror;
  781. }
  782. /**
  783. * Send an SMTP RCPT command.
  784. * Sets the TO argument to $toaddr.
  785. * Returns true if the recipient was accepted false if it was rejected.
  786. * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
  787. * @param string $address The address the message is being sent to
  788. * @access public
  789. * @return boolean
  790. */
  791. public function recipient($address)
  792. {
  793. return $this->sendCommand(
  794. 'RCPT TO',
  795. 'RCPT TO:<' . $address . '>',
  796. array(250, 251)
  797. );
  798. }
  799. /**
  800. * Send an SMTP RSET command.
  801. * Abort any transaction that is currently in progress.
  802. * Implements rfc 821: RSET <CRLF>
  803. * @access public
  804. * @return boolean True on success.
  805. */
  806. public function reset()
  807. {
  808. return $this->sendCommand('RSET', 'RSET', 250);
  809. }
  810. /**
  811. * Send a command to an SMTP server and check its return code.
  812. * @param string $command The command name - not sent to the server
  813. * @param string $commandstring The actual command to send
  814. * @param integer|array $expect One or more expected integer success codes
  815. * @access protected
  816. * @return boolean True on success.
  817. */
  818. protected function sendCommand($command, $commandstring, $expect)
  819. {
  820. if (!$this->connected()) {
  821. $this->setError("Called $command without being connected");
  822. return false;
  823. }
  824. //Reject line breaks in all commands
  825. if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
  826. $this->setError("Command '$command' contained line breaks");
  827. return false;
  828. }
  829. $this->client_send($commandstring . self::CRLF);
  830. $this->last_reply = $this->get_lines();
  831. // Fetch SMTP code and possible error code explanation
  832. $matches = array();
  833. if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
  834. $code = $matches[1];
  835. $code_ex = (count($matches) > 2 ? $matches[2] : null);
  836. // Cut off error code from each response line
  837. $detail = preg_replace(
  838. "/{$code}[ -]" .
  839. ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m",
  840. '',
  841. $this->last_reply
  842. );
  843. } else {
  844. // Fall back to simple parsing if regex fails
  845. $code = substr($this->last_reply, 0, 3);
  846. $code_ex = null;
  847. $detail = substr($this->last_reply, 4);
  848. }
  849. $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
  850. if (!in_array($code, (array)$expect)) {
  851. $this->setError(
  852. "$command command failed",
  853. $detail,
  854. $code,
  855. $code_ex
  856. );
  857. $this->edebug(
  858. 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
  859. self::DEBUG_CLIENT
  860. );
  861. return false;
  862. }
  863. $this->setError('');
  864. return true;
  865. }
  866. /**
  867. * Send an SMTP SAML command.
  868. * Starts a mail transaction from the email address specified in $from.
  869. * Returns true if successful or false otherwise. If True
  870. * the mail transaction is started and then one or more recipient
  871. * commands may be called followed by a data command. This command
  872. * will send the message to the users terminal if they are logged
  873. * in and send them an email.
  874. * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
  875. * @param string $from The address the message is from
  876. * @access public
  877. * @return boolean
  878. */
  879. public function sendAndMail($from)
  880. {
  881. return $this->sendCommand('SAML', "SAML FROM:$from", 250);
  882. }
  883. /**
  884. * Send an SMTP VRFY command.
  885. * @param string $name The name to verify
  886. * @access public
  887. * @return boolean
  888. */
  889. public function verify($name)
  890. {
  891. return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
  892. }
  893. /**
  894. * Send an SMTP NOOP command.
  895. * Used to keep keep-alives alive, doesn't actually do anything
  896. * @access public
  897. * @return boolean
  898. */
  899. public function noop()
  900. {
  901. return $this->sendCommand('NOOP', 'NOOP', 250);
  902. }
  903. /**
  904. * Send an SMTP TURN command.
  905. * This is an optional command for SMTP that this class does not support.
  906. * This method is here to make the RFC821 Definition complete for this class
  907. * and _may_ be implemented in future
  908. * Implements from rfc 821: TURN <CRLF>
  909. * @access public
  910. * @return boolean
  911. */
  912. public function turn()
  913. {
  914. $this->setError('The SMTP TURN command is not implemented');
  915. $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
  916. return false;
  917. }
  918. /**
  919. * Send raw data to the server.
  920. * @param string $data The data to send
  921. * @access public
  922. * @return integer|boolean The number of bytes sent to the server or false on error
  923. */
  924. public function client_send($data)
  925. {
  926. $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
  927. set_error_handler(array($this, 'errorHandler'));
  928. $result = fwrite($this->smtp_conn, $data);
  929. restore_error_handler();
  930. return $result;
  931. }
  932. /**
  933. * Get the latest error.
  934. * @access public
  935. * @return array
  936. */
  937. public function getError()
  938. {
  939. return $this->error;
  940. }
  941. /**
  942. * Get SMTP extensions available on the server
  943. * @access public
  944. * @return array|null
  945. */
  946. public function getServerExtList()
  947. {
  948. return $this->server_caps;
  949. }
  950. /**
  951. * A multipurpose method
  952. * The method works in three ways, dependent on argument value and current state
  953. * 1. HELO/EHLO was not sent - returns null and set up $this->error
  954. * 2. HELO was sent
  955. * $name = 'HELO': returns server name
  956. * $name = 'EHLO': returns boolean false
  957. * $name = any string: returns null and set up $this->error
  958. * 3. EHLO was sent
  959. * $name = 'HELO'|'EHLO': returns server name
  960. * $name = any string: if extension $name exists, returns boolean True
  961. * or its options. Otherwise returns boolean False
  962. * In other words, one can use this method to detect 3 conditions:
  963. * - null returned: handshake was not or we don't know about ext (refer to $this->error)
  964. * - false returned: the requested feature exactly not exists
  965. * - positive value returned: the requested feature exists
  966. * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
  967. * @return mixed
  968. */
  969. public function getServerExt($name)
  970. {
  971. if (!$this->server_caps) {
  972. $this->setError('No HELO/EHLO was sent');
  973. return null;
  974. }
  975. // the tight logic knot ;)
  976. if (!array_key_exists($name, $this->server_caps)) {
  977. if ($name == 'HELO') {
  978. return $this->server_caps['EHLO'];
  979. }
  980. if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
  981. return false;
  982. }
  983. $this->setError('HELO handshake was used. Client knows nothing about server extensions');
  984. return null;
  985. }
  986. return $this->server_caps[$name];
  987. }
  988. /**
  989. * Get the last reply from the server.
  990. * @access public
  991. * @return string
  992. */
  993. public function getLastReply()
  994. {
  995. return $this->last_reply;
  996. }
  997. /**
  998. * Read the SMTP server's response.
  999. * Either before eof or socket timeout occurs on the operation.
  1000. * With SMTP we can tell if we have more lines to read if the
  1001. * 4th character is '-' symbol. If it is a space then we don't
  1002. * need to read anything else.
  1003. * @access protected
  1004. * @return string
  1005. */
  1006. protected function get_lines()
  1007. {
  1008. // If the connection is bad, give up straight away
  1009. if (!is_resource($this->smtp_conn)) {
  1010. return '';
  1011. }
  1012. $data = '';
  1013. $endtime = 0;
  1014. stream_set_timeout($this->smtp_conn, $this->Timeout);
  1015. if ($this->Timelimit > 0) {
  1016. $endtime = time() + $this->Timelimit;
  1017. }
  1018. while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
  1019. $str = @fgets($this->smtp_conn, 515);
  1020. $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
  1021. $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
  1022. $data .= $str;
  1023. // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
  1024. // or 4th character is a space, we are done reading, break the loop,
  1025. // string array access is a micro-optimisation over strlen
  1026. if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
  1027. break;
  1028. }
  1029. // Timed-out? Log and break
  1030. $info = stream_get_meta_data($this->smtp_conn);
  1031. if ($info['timed_out']) {
  1032. $this->edebug(
  1033. 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
  1034. self::DEBUG_LOWLEVEL
  1035. );
  1036. break;
  1037. }
  1038. // Now check if reads took too long
  1039. if ($endtime and time() > $endtime) {
  1040. $this->edebug(
  1041. 'SMTP -> get_lines(): timelimit reached (' .
  1042. $this->Timelimit . ' sec)',
  1043. self::DEBUG_LOWLEVEL
  1044. );
  1045. break;
  1046. }
  1047. }
  1048. return $data;
  1049. }
  1050. /**
  1051. * Enable or disable VERP address generation.
  1052. * @param boolean $enabled
  1053. */
  1054. public function setVerp($enabled = false)
  1055. {
  1056. $this->do_verp = $enabled;
  1057. }
  1058. /**
  1059. * Get VERP address generation mode.
  1060. * @return boolean
  1061. */
  1062. public function getVerp()
  1063. {
  1064. return $this->do_verp;
  1065. }
  1066. /**
  1067. * Set error messages and codes.
  1068. * @param string $message The error message
  1069. * @param string $detail Further detail on the error
  1070. * @param string $smtp_code An associated SMTP error code
  1071. * @param string $smtp_code_ex Extended SMTP code
  1072. */
  1073. protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
  1074. {
  1075. $this->error = array(
  1076. 'error' => $message,
  1077. 'detail' => $detail,
  1078. 'smtp_code' => $smtp_code,
  1079. 'smtp_code_ex' => $smtp_code_ex
  1080. );
  1081. }
  1082. /**
  1083. * Set debug output method.
  1084. * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
  1085. */
  1086. public function setDebugOutput($method = 'echo')
  1087. {
  1088. $this->Debugoutput = $method;
  1089. }
  1090. /**
  1091. * Get debug output method.
  1092. * @return string
  1093. */
  1094. public function getDebugOutput()
  1095. {
  1096. return $this->Debugoutput;
  1097. }
  1098. /**
  1099. * Set debug output level.
  1100. * @param integer $level
  1101. */
  1102. public function setDebugLevel($level = 0)
  1103. {
  1104. $this->do_debug = $level;
  1105. }
  1106. /**
  1107. * Get debug output level.
  1108. * @return integer
  1109. */
  1110. public function getDebugLevel()
  1111. {
  1112. return $this->do_debug;
  1113. }
  1114. /**
  1115. * Set SMTP timeout.
  1116. * @param integer $timeout
  1117. */
  1118. public function setTimeout($timeout = 0)
  1119. {
  1120. $this->Timeout = $timeout;
  1121. }
  1122. /**
  1123. * Get SMTP timeout.
  1124. * @return integer
  1125. */
  1126. public function getTimeout()
  1127. {
  1128. return $this->Timeout;
  1129. }
  1130. /**
  1131. * Reports an error number and string.
  1132. * @param integer $errno The error number returned by PHP.
  1133. * @param string $errmsg The error message returned by PHP.
  1134. * @param string $errfile The file the error occurred in
  1135. * @param integer $errline The line number the error occurred on
  1136. */
  1137. protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
  1138. {
  1139. $notice = 'Connection failed.';
  1140. $this->setError(
  1141. $notice,
  1142. $errno,
  1143. $errmsg
  1144. );
  1145. $this->edebug(
  1146. $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]",
  1147. self::DEBUG_CONNECTION
  1148. );
  1149. }
  1150. /**
  1151. * Extract and return the ID of the last SMTP transaction based on
  1152. * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
  1153. * Relies on the host providing the ID in response to a DATA command.
  1154. * If no reply has been received yet, it will return null.
  1155. * If no pattern was matched, it will return false.
  1156. * @return bool|null|string
  1157. */
  1158. protected function recordLastTransactionID()
  1159. {
  1160. $reply = $this->getLastReply();
  1161. if (empty($reply)) {
  1162. $this->last_smtp_transaction_id = null;
  1163. } else {
  1164. $this->last_smtp_transaction_id = false;
  1165. foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
  1166. if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
  1167. $this->last_smtp_transaction_id = $matches[1];
  1168. }
  1169. }
  1170. }
  1171. return $this->last_smtp_transaction_id;
  1172. }
  1173. /**
  1174. * Get the queue/transaction ID of the last SMTP transaction
  1175. * If no reply has been received yet, it will return null.
  1176. * If no pattern was matched, it will return false.
  1177. * @return bool|null|string
  1178. * @see recordLastTransactionID()
  1179. */
  1180. public function getLastTransactionID()
  1181. {
  1182. return $this->last_smtp_transaction_id;
  1183. }
  1184. }