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