Changeset 19942 for branches/version-2_5-dev/data/module/Net/SMTP.php
- Timestamp:
- 2011/01/17 14:46:37 (13 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/version-2_5-dev/data/module/Net/SMTP.php
r16300 r19942 19 19 // +----------------------------------------------------------------------+ 20 20 // 21 // $Id: SMTP.php,v 1.58 2007/03/28 04:53:34 chagenbu Exp $ 22 23 $include_dir = realpath(dirname( __FILE__)); 24 require_once $include_dir . "/../PEAR.php"; 25 require_once $include_dir . "/../Net/Socket.php"; 21 // $Id: SMTP.php 304535 2010-10-20 06:48:06Z jon $ 22 23 require_once 'PEAR.php'; 24 require_once 'Net/Socket.php'; 26 25 27 26 /** … … 67 66 68 67 /** 68 * Use SMTP command pipelining (specified in RFC 2920) if the SMTP 69 * server supports it. 70 * 71 * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(), 72 * somlFrom() and samlFrom() do not wait for a response from the 73 * SMTP server but return immediately. 74 * 75 * @var bool 76 * @access public 77 */ 78 var $pipelining = false; 79 80 /** 81 * Number of pipelined commands. 82 * @var int 83 * @access private 84 */ 85 var $_pipelined_commands = 0; 86 87 /** 69 88 * Should debugging output be enabled? 70 89 * @var boolean … … 74 93 75 94 /** 95 * Debug output handler. 96 * @var callback 97 * @access private 98 */ 99 var $_debug_handler = null; 100 101 /** 76 102 * The socket resource being used to connect to the SMTP server. 77 103 * @var resource … … 93 119 */ 94 120 var $_arguments = array(); 121 122 /** 123 * Stores the SMTP server's greeting string. 124 * @var string 125 * @access private 126 */ 127 var $_greeting = null; 95 128 96 129 /** … … 115 148 * @param integer $port The port to connect to. 116 149 * @param string $localhost The value to give when sending EHLO or HELO. 150 * @param boolean $pipeling Use SMTP command pipelining 117 151 * 118 152 * @access public 119 153 * @since 1.0 120 154 */ 121 function Net_SMTP($host = null, $port = null, $localhost = null) 122 { 123 if (isset($host)) $this->host = $host; 124 if (isset($port)) $this->port = $port; 125 if (isset($localhost)) $this->localhost = $localhost; 155 function Net_SMTP($host = null, $port = null, $localhost = null, $pipelining = false) 156 { 157 if (isset($host)) { 158 $this->host = $host; 159 } 160 if (isset($port)) { 161 $this->port = $port; 162 } 163 if (isset($localhost)) { 164 $this->localhost = $localhost; 165 } 166 $this->pipelining = $pipelining; 126 167 127 168 $this->_socket = new Net_Socket(); 128 169 129 /* 130 * Include the Auth_SASL package. If the package is not available, 131 * we disable the authentication methods that depend upon it. 132 */ 133 if ((@include_once '../Auth/SASL.php') === false) { 170 /* Include the Auth_SASL package. If the package is not 171 * available, we disable the authentication methods that 172 * depend upon it. */ 173 if ((@include_once 'Auth/SASL.php') === false) { 134 174 $pos = array_search('DIGEST-MD5', $this->auth_methods); 135 175 unset($this->auth_methods[$pos]); … … 147 187 * @since 1.1.0 148 188 */ 149 function setDebug($debug )189 function setDebug($debug, $handler = null) 150 190 { 151 191 $this->_debug = $debug; 192 $this->_debug_handler = $handler; 193 } 194 195 /** 196 * Write the given debug text to the current debug output handler. 197 * 198 * @param string $message Debug mesage text. 199 * 200 * @access private 201 * @since 1.3.3 202 */ 203 function _debug($message) 204 { 205 if ($this->_debug) { 206 if ($this->_debug_handler) { 207 call_user_func_array($this->_debug_handler, 208 array(&$this, $message)); 209 } else { 210 echo "DEBUG: $message\n"; 211 } 212 } 152 213 } 153 214 … … 164 225 function _send($data) 165 226 { 166 if ($this->_debug) { 167 echo "DEBUG: Send: $data\n"; 168 } 169 170 if (PEAR::isError($error = $this->_socket->write($data))) { 171 return PEAR::raiseError('Failed to write to socket: ' . 172 $error->getMessage()); 227 $this->_debug("Send: $data"); 228 229 $error = $this->_socket->write($data); 230 if ($error === false || PEAR::isError($error)) { 231 $msg = ($error) ? $error->getMessage() : "unknown error"; 232 return PEAR::raiseError("Failed to write to socket: $msg"); 173 233 } 174 234 … … 213 273 * may be specified as an array of integer 214 274 * values or as a single integer value. 275 * @param bool $later Do not parse the response now, but wait 276 * until the last command in the pipelined 277 * command group 215 278 * 216 279 * @return mixed True if the server returned a valid response code or … … 222 285 * @see getResponse 223 286 */ 224 function _parseResponse($valid )287 function _parseResponse($valid, $later = false) 225 288 { 226 289 $this->_code = -1; 227 290 $this->_arguments = array(); 228 291 229 while ($line = $this->_socket->readLine()) { 230 if ($this->_debug) { 231 echo "DEBUG: Recv: $line\n"; 232 } 233 234 /* If we receive an empty line, the connection has been closed. */ 235 if (empty($line)) { 236 $this->disconnect(); 237 return PEAR::raiseError('Connection was unexpectedly closed'); 238 } 239 240 /* Read the code and store the rest in the arguments array. */ 241 $code = substr($line, 0, 3); 242 $this->_arguments[] = trim(substr($line, 4)); 243 244 /* Check the syntax of the response code. */ 245 if (is_numeric($code)) { 246 $this->_code = (int)$code; 247 } else { 248 $this->_code = -1; 249 break; 250 } 251 252 /* If this is not a multiline response, we're done. */ 253 if (substr($line, 3, 1) != '-') { 254 break; 255 } 256 } 257 258 /* Compare the server's response code with the valid code. */ 292 if ($later) { 293 $this->_pipelined_commands++; 294 return true; 295 } 296 297 for ($i = 0; $i <= $this->_pipelined_commands; $i++) { 298 while ($line = $this->_socket->readLine()) { 299 $this->_debug("Recv: $line"); 300 301 /* If we receive an empty line, the connection has been closed. */ 302 if (empty($line)) { 303 $this->disconnect(); 304 return PEAR::raiseError('Connection was unexpectedly closed'); 305 } 306 307 /* Read the code and store the rest in the arguments array. */ 308 $code = substr($line, 0, 3); 309 $this->_arguments[] = trim(substr($line, 4)); 310 311 /* Check the syntax of the response code. */ 312 if (is_numeric($code)) { 313 $this->_code = (int)$code; 314 } else { 315 $this->_code = -1; 316 break; 317 } 318 319 /* If this is not a multiline response, we're done. */ 320 if (substr($line, 3, 1) != '-') { 321 break; 322 } 323 } 324 } 325 326 $this->_pipelined_commands = 0; 327 328 /* Compare the server's response code with the valid code/codes. */ 259 329 if (is_int($valid) && ($this->_code === $valid)) { 260 330 return true; 261 } 262 263 /* If we were given an array of valid response codes, check each one. */ 264 if (is_array($valid)) { 265 foreach ($valid as $valid_code) { 266 if ($this->_code === $valid_code) { 267 return true; 268 } 269 } 331 } elseif (is_array($valid) && in_array($this->_code, $valid, true)) { 332 return true; 270 333 } 271 334 … … 287 350 { 288 351 return array($this->_code, join("\n", $this->_arguments)); 352 } 353 354 /** 355 * Return the SMTP server's greeting string. 356 * 357 * @return string A string containing the greeting string, or null if a 358 * greeting has not been received. 359 * 360 * @access public 361 * @since 1.3.3 362 */ 363 function getGreeting() 364 { 365 return $this->_greeting; 289 366 } 290 367 … … 304 381 function connect($timeout = null, $persistent = false) 305 382 { 383 $this->_greeting = null; 306 384 $result = $this->_socket->connect($this->host, $this->port, 307 385 $persistent, $timeout); … … 314 392 return $error; 315 393 } 394 395 /* Extract and store a copy of the server's greeting string. */ 396 list(, $this->_greeting) = $this->getResponse(); 397 316 398 if (PEAR::isError($error = $this->_negotiate())) { 317 399 return $error; … … 385 467 } 386 468 469 if (!isset($this->_esmtp['PIPELINING'])) { 470 $this->pipelining = false; 471 } 472 387 473 return true; 388 474 } … … 418 504 * @param string The requested authentication method. If none is 419 505 * specified, the best supported method will be used. 506 * @param bool Flag indicating whether or not TLS should be attempted. 507 * @param string An optional authorization identifier. If specified, this 508 * identifier will be used as the authorization proxy. 420 509 * 421 510 * @return mixed Returns a PEAR_Error with an error message on any … … 424 513 * @since 1.0 425 514 */ 426 function auth($uid, $pwd , $method = '') 427 { 515 function auth($uid, $pwd , $method = '', $tls = true, $authz = '') 516 { 517 /* We can only attempt a TLS connection if one has been requested, 518 * we're running PHP 5.1.0 or later, have access to the OpenSSL 519 * extension, are connected to an SMTP server which supports the 520 * STARTTLS extension, and aren't already connected over a secure 521 * (SSL) socket connection. */ 522 if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') && 523 extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) && 524 strncasecmp($this->host, 'ssl://', 6) !== 0) { 525 /* Start the TLS connection attempt. */ 526 if (PEAR::isError($result = $this->_put('STARTTLS'))) { 527 return $result; 528 } 529 if (PEAR::isError($result = $this->_parseResponse(220))) { 530 return $result; 531 } 532 if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { 533 return $result; 534 } elseif ($result !== true) { 535 return PEAR::raiseError('STARTTLS failed'); 536 } 537 538 /* Send EHLO again to recieve the AUTH string from the 539 * SMTP server. */ 540 $this->_negotiate(); 541 } 542 428 543 if (empty($this->_esmtp['AUTH'])) { 429 if (version_compare(PHP_VERSION, '5.1.0', '>=')) { 430 if (!isset($this->_esmtp['STARTTLS'])) { 431 return PEAR::raiseError('SMTP server does not support authentication'); 432 } 433 if (PEAR::isError($result = $this->_put('STARTTLS'))) { 434 return $result; 435 } 436 if (PEAR::isError($result = $this->_parseResponse(220))) { 437 return $result; 438 } 439 if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { 440 return $result; 441 } elseif ($result !== true) { 442 return PEAR::raiseError('STARTTLS failed'); 443 } 444 445 /* Send EHLO again to recieve the AUTH string from the 446 * SMTP server. */ 447 $this->_negotiate(); 448 if (empty($this->_esmtp['AUTH'])) { 449 return PEAR::raiseError('SMTP server does not support authentication'); 450 } 451 } else { 452 return PEAR::raiseError('SMTP server does not support authentication'); 453 } 544 return PEAR::raiseError('SMTP server does not support authentication'); 454 545 } 455 546 … … 470 561 switch ($method) { 471 562 case 'DIGEST-MD5': 472 $result = $this->_authDigest_MD5($uid, $pwd );563 $result = $this->_authDigest_MD5($uid, $pwd, $authz); 473 564 break; 474 565 … … 482 573 483 574 case 'PLAIN': 484 $result = $this->_authPlain($uid, $pwd );575 $result = $this->_authPlain($uid, $pwd, $authz); 485 576 break; 486 577 … … 503 594 * @param string The userid to authenticate as. 504 595 * @param string The password to authenticate with. 596 * @param string The optional authorization proxy identifier. 505 597 * 506 598 * @return mixed Returns a PEAR_Error with an error message on any … … 509 601 * @since 1.1.0 510 602 */ 511 function _authDigest_MD5($uid, $pwd )603 function _authDigest_MD5($uid, $pwd, $authz = '') 512 604 { 513 605 if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) { … … 539 631 * allow subsequent authentication, so we just silently ignore 540 632 * it. */ 541 if (PEAR::isError($error = $this->_put(' 633 if (PEAR::isError($error = $this->_put(''))) { 542 634 return $error; 543 635 } … … 637 729 * @param string The userid to authenticate as. 638 730 * @param string The password to authenticate with. 731 * @param string The optional authorization proxy identifier. 639 732 * 640 733 * @return mixed Returns a PEAR_Error with an error message on any … … 643 736 * @since 1.1.0 644 737 */ 645 function _authPlain($uid, $pwd )738 function _authPlain($uid, $pwd, $authz = '') 646 739 { 647 740 if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) { … … 657 750 } 658 751 659 $auth_str = base64_encode( chr(0) . $uid . chr(0) . $pwd);752 $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd); 660 753 661 754 if (PEAR::isError($error = $this->_put($auth_str))) { … … 691 784 692 785 return true; 786 } 787 788 /** 789 * Return the list of SMTP service extensions advertised by the server. 790 * 791 * @return array The list of SMTP service extensions. 792 * @access public 793 * @since 1.3 794 */ 795 function getServiceExtensions() 796 { 797 return $this->_esmtp; 693 798 } 694 799 … … 733 838 return $error; 734 839 } 735 if (PEAR::isError($error = $this->_parseResponse(250 ))) {840 if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { 736 841 return $error; 737 842 } … … 763 868 return $error; 764 869 } 765 if (PEAR::isError($error = $this->_parseResponse(array(250, 251) ))) {870 if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) { 766 871 return $error; 767 872 } … … 798 903 * Send the DATA command. 799 904 * 800 * @param string $data The message body to send. 905 * @param mixed $data The message data, either as a string or an open 906 * file resource. 907 * @param string $headers The message headers. If $headers is provided, 908 * $data is assumed to contain only body data. 801 909 * 802 910 * @return mixed Returns a PEAR_Error with an error message on any … … 805 913 * @since 1.0 806 914 */ 807 function data($data) 808 { 809 /* RFC 1870, section 3, subsection 3 states "a value of zero 810 * indicates that no fixed maximum message size is in force". 811 * Furthermore, it says that if "the parameter is omitted no 812 * information is conveyed about the server's fixed maximum 813 * message size". */ 814 if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) { 815 if (strlen($data) >= $this->_esmtp['SIZE']) { 816 $this->disconnect(); 817 return PEAR::raiseError('Message size excedes the server limit'); 818 } 819 } 820 821 /* Quote the data based on the SMTP standards. */ 822 $this->quotedata($data); 823 915 function data($data, $headers = null) 916 { 917 /* Verify that $data is a supported type. */ 918 if (!is_string($data) && !is_resource($data)) { 919 return PEAR::raiseError('Expected a string or file resource'); 920 } 921 922 /* Start by considering the size of the optional headers string. We 923 * also account for the addition 4 character "\r\n\r\n" separator 924 * sequence. */ 925 $size = (is_null($headers)) ? 0 : strlen($headers) + 4; 926 927 if (is_resource($data)) { 928 $stat = fstat($data); 929 if ($stat === false) { 930 return PEAR::raiseError('Failed to get file size'); 931 } 932 $size += $stat['size']; 933 } else { 934 $size += strlen($data); 935 } 936 937 /* RFC 1870, section 3, subsection 3 states "a value of zero indicates 938 * that no fixed maximum message size is in force". Furthermore, it 939 * says that if "the parameter is omitted no information is conveyed 940 * about the server's fixed maximum message size". */ 941 $limit = (isset($this->_esmtp['SIZE'])) ? $this->_esmtp['SIZE'] : 0; 942 if ($limit > 0 && $size >= $limit) { 943 $this->disconnect(); 944 return PEAR::raiseError('Message size exceeds server limit'); 945 } 946 947 /* Initiate the DATA command. */ 824 948 if (PEAR::isError($error = $this->_put('DATA'))) { 825 949 return $error; … … 829 953 } 830 954 831 if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) { 955 /* If we have a separate headers string, send it first. */ 956 if (!is_null($headers)) { 957 $this->quotedata($headers); 958 if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) { 959 return $result; 960 } 961 } 962 963 /* Now we can send the message body data. */ 964 if (is_resource($data)) { 965 /* Stream the contents of the file resource out over our socket 966 * connection, line by line. Each line must be run through the 967 * quoting routine. */ 968 while ($line = fgets($data, 1024)) { 969 $this->quotedata($line); 970 if (PEAR::isError($result = $this->_send($line))) { 971 return $result; 972 } 973 } 974 } else { 975 /* 976 * Break up the data by sending one chunk (up to 512k) at a time. 977 * This approach reduces our peak memory usage. 978 */ 979 for ($offset = 0; $offset < $size;) { 980 $end = $offset + 512000; 981 982 /* 983 * Ensure we don't read beyond our data size or span multiple 984 * lines. quotedata() can't properly handle character data 985 * that's split across two line break boundaries. 986 */ 987 if ($end >= $size) { 988 $end = $size; 989 } else { 990 for (; $end < $size; $end++) { 991 if ($data[$end] != "\n") { 992 break; 993 } 994 } 995 } 996 997 /* Extract our chunk and run it through the quoting routine. */ 998 $chunk = substr($data, $offset, $end - $offset); 999 $this->quotedata($chunk); 1000 1001 /* If we run into a problem along the way, abort. */ 1002 if (PEAR::isError($result = $this->_send($chunk))) { 1003 return $result; 1004 } 1005 1006 /* Advance the offset to the end of this chunk. */ 1007 $offset = $end; 1008 } 1009 } 1010 1011 /* Finally, send the DATA terminator sequence. */ 1012 if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) { 832 1013 return $result; 833 1014 } 834 if (PEAR::isError($error = $this->_parseResponse(250))) { 1015 1016 /* Verify that the data was successfully received by the server. */ 1017 if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { 835 1018 return $error; 836 1019 } … … 854 1037 return $error; 855 1038 } 856 if (PEAR::isError($error = $this->_parseResponse(250 ))) {1039 if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { 857 1040 return $error; 858 1041 } … … 893 1076 return $error; 894 1077 } 895 if (PEAR::isError($error = $this->_parseResponse(250 ))) {1078 if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { 896 1079 return $error; 897 1080 } … … 932 1115 return $error; 933 1116 } 934 if (PEAR::isError($error = $this->_parseResponse(250 ))) {1117 if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { 935 1118 return $error; 936 1119 } … … 969 1152 return $error; 970 1153 } 971 if (PEAR::isError($error = $this->_parseResponse(250 ))) {1154 if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { 972 1155 return $error; 973 1156 }
Note: See TracChangeset
for help on using the changeset viewer.