2 /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
3 // +----------------------------------------------------------------------+
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2003 The PHP Group |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license, |
9 // | that is bundled with this package in the file LICENSE, and is |
10 // | available at through the world-wide-web at |
11 // | http://www.php.net/license/2_02.txt. |
12 // | If you did not receive a copy of the PHP license and are unable to |
13 // | obtain it through the world-wide-web, please send a note to |
14 // | license@php.net so we can mail you a copy immediately. |
15 // +----------------------------------------------------------------------+
16 // | Authors: Chuck Hagenbuch <chuck@horde.org> |
17 // | Jon Parise <jon@php.net> |
18 // | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
19 // +----------------------------------------------------------------------+
21 // $Id: SMTP.php 399 2006-12-06 21:37:37Z thomasb $
23 require_once 'PEAR.php';
24 require_once 'Net/Socket.php';
27 * Provides an implementation of the SMTP protocol using PEAR's
31 * @author Chuck Hagenbuch <chuck@horde.org>
32 * @author Jon Parise <jon@php.net>
33 * @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
35 * @example basic.php A basic implementation of the Net_SMTP package.
41 * The server to connect to.
45 var $host = 'localhost';
48 * The port to connect to.
55 * The value to give when sending EHLO or HELO.
59 var $localhost = 'localhost';
62 * List of supported authentication methods, in preferential order.
66 var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
69 * Should debugging output be enabled?
76 * The socket resource being used to connect to the SMTP server.
83 * The most recent server response code.
90 * The most recent server response arguments.
94 var $_arguments = array();
97 * Stores detected features of the SMTP server.
101 var $_esmtp = array();
104 * Instantiates a new Net_SMTP object, overriding any defaults
105 * with parameters that are passed in.
107 * If you have SSL support in PHP, you can connect to a server
108 * over SSL using an 'ssl://' prefix:
110 * // 465 is a common smtps port.
111 * $smtp = new Net_SMTP('ssl://mail.host.com', 465);
114 * @param string $host The server to connect to.
115 * @param integer $port The port to connect to.
116 * @param string $localhost The value to give when sending EHLO or HELO.
121 function Net_SMTP($host = null, $port = null, $localhost = null)
123 if (isset($host)) $this->host = $host;
124 if (isset($port)) $this->port = $port;
125 if (isset($localhost)) $this->localhost = $localhost;
127 $this->_socket = &new Net_Socket();
130 * Include the Auth_SASL package. If the package is not available,
131 * we disable the authentication methods that depend upon it.
133 if ((@include_once 'Auth/SASL.php') === false) {
134 $pos = array_search('DIGEST-MD5', $this->auth_methods);
135 unset($this->auth_methods[$pos]);
136 $pos = array_search('CRAM-MD5', $this->auth_methods);
137 unset($this->auth_methods[$pos]);
142 * Set the value of the debugging flag.
144 * @param boolean $debug New value for the debugging flag.
149 function setDebug($debug)
151 $this->_debug = $debug;
155 * Send the given string of data to the server.
157 * @param string $data The string of data to send.
159 * @return mixed True on success or a PEAR_Error object on failure.
164 function _send($data)
167 echo "DEBUG: Send: $data\n";
170 if (PEAR::isError($error = $this->_socket->write($data))) {
171 return PEAR::raiseError('Failed to write to socket: ' .
172 $error->getMessage());
179 * Send a command to the server with an optional string of
180 * arguments. A carriage return / linefeed (CRLF) sequence will
181 * be appended to each command string before it is sent to the
182 * SMTP server - an error will be thrown if the command string
183 * already contains any newline characters. Use _send() for
184 * commands that must contain newlines.
186 * @param string $command The SMTP command to send to the server.
187 * @param string $args A string of optional arguments to append
190 * @return mixed The result of the _send() call.
195 function _put($command, $args = '')
198 $command .= ' ' . $args;
201 if (strcspn($command, "\r\n") !== strlen($command)) {
202 return PEAR::raiseError('Commands cannot contain newlines');
205 return $this->_send($command . "\r\n");
209 * Read a reply from the SMTP server. The reply consists of a response
210 * code and a response message.
212 * @param mixed $valid The set of valid response codes. These
213 * may be specified as an array of integer
214 * values or as a single integer value.
216 * @return mixed True if the server returned a valid response code or
217 * a PEAR_Error object is an error condition is reached.
224 function _parseResponse($valid)
227 $this->_arguments = array();
229 while ($line = $this->_socket->readLine()) {
231 echo "DEBUG: Recv: $line\n";
234 /* If we receive an empty line, the connection has been closed. */
237 return PEAR::raiseError('Connection was unexpectedly closed');
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));
244 /* Check the syntax of the response code. */
245 if (is_numeric($code)) {
246 $this->_code = (int)$code;
252 /* If this is not a multiline response, we're done. */
253 if (substr($line, 3, 1) != '-') {
258 /* Compare the server's response code with the valid code. */
259 if (is_int($valid) && ($this->_code === $valid)) {
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) {
272 return PEAR::raiseError('Invalid response code received from server');
276 * Return a 2-tuple containing the last response from the SMTP server.
278 * @return array A two-element array: the first element contains the
279 * response code as an integer and the second element
280 * contains the response's arguments as a string.
285 function getResponse()
287 return array($this->_code, join("\n", $this->_arguments));
291 * Attempt to connect to the SMTP server.
293 * @param int $timeout The timeout value (in seconds) for the
295 * @param bool $persistent Should a persistent socket connection
298 * @return mixed Returns a PEAR_Error with an error message on any
299 * kind of failure, or true on success.
303 function connect($timeout = null, $persistent = false)
305 $result = $this->_socket->connect($this->host, $this->port,
306 $persistent, $timeout);
307 if (PEAR::isError($result)) {
308 return PEAR::raiseError('Failed to connect socket: ' .
309 $result->getMessage());
312 if (PEAR::isError($error = $this->_parseResponse(220))) {
315 if (PEAR::isError($error = $this->_negotiate())) {
323 * Attempt to disconnect from the SMTP server.
325 * @return mixed Returns a PEAR_Error with an error message on any
326 * kind of failure, or true on success.
330 function disconnect()
332 if (PEAR::isError($error = $this->_put('QUIT'))) {
335 if (PEAR::isError($error = $this->_parseResponse(221))) {
338 if (PEAR::isError($error = $this->_socket->disconnect())) {
339 return PEAR::raiseError('Failed to disconnect socket: ' .
340 $error->getMessage());
347 * Attempt to send the EHLO command and obtain a list of ESMTP
348 * extensions available, and failing that just send HELO.
350 * @return mixed Returns a PEAR_Error with an error message on any
351 * kind of failure, or true on success.
356 function _negotiate()
358 if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
362 if (PEAR::isError($this->_parseResponse(250))) {
363 /* If we receive a 503 response, we're already authenticated. */
364 if ($this->_code === 503) {
368 /* If the EHLO failed, try the simpler HELO command. */
369 if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
372 if (PEAR::isError($this->_parseResponse(250))) {
373 return PEAR::raiseError('HELO was not accepted: ', $this->_code);
379 foreach ($this->_arguments as $argument) {
380 $verb = strtok($argument, ' ');
381 $arguments = substr($argument, strlen($verb) + 1,
382 strlen($argument) - strlen($verb) - 1);
383 $this->_esmtp[$verb] = $arguments;
390 * Returns the name of the best authentication method that the server
393 * @return mixed Returns a string containing the name of the best
394 * supported authentication method or a PEAR_Error object
395 * if a failure condition is encountered.
399 function _getBestAuthMethod()
401 $available_methods = explode(' ', $this->_esmtp['AUTH']);
403 foreach ($this->auth_methods as $method) {
404 if (in_array($method, $available_methods)) {
409 return PEAR::raiseError('No supported authentication methods');
413 * Attempt to do SMTP authentication.
415 * @param string The userid to authenticate as.
416 * @param string The password to authenticate with.
417 * @param string The requested authentication method. If none is
418 * specified, the best supported method will be used.
420 * @return mixed Returns a PEAR_Error with an error message on any
421 * kind of failure, or true on success.
425 function auth($uid, $pwd , $method = '')
427 if (empty($this->_esmtp['AUTH'])) {
428 return PEAR::raiseError('SMTP server does no support authentication');
431 /* If no method has been specified, get the name of the best
432 * supported method advertised by the SMTP server. */
433 if (empty($method)) {
434 if (PEAR::isError($method = $this->_getBestAuthMethod())) {
435 /* Return the PEAR_Error object from _getBestAuthMethod(). */
439 $method = strtoupper($method);
440 if (!in_array($method, $this->auth_methods)) {
441 return PEAR::raiseError("$method is not a supported authentication method");
447 $result = $this->_authDigest_MD5($uid, $pwd);
450 $result = $this->_authCRAM_MD5($uid, $pwd);
453 $result = $this->_authLogin($uid, $pwd);
456 $result = $this->_authPlain($uid, $pwd);
459 $result = PEAR::raiseError("$method is not a supported authentication method");
463 /* If an error was encountered, return the PEAR_Error object. */
464 if (PEAR::isError($result)) {
472 * Authenticates the user using the DIGEST-MD5 method.
474 * @param string The userid to authenticate as.
475 * @param string The password to authenticate with.
477 * @return mixed Returns a PEAR_Error with an error message on any
478 * kind of failure, or true on success.
482 function _authDigest_MD5($uid, $pwd)
484 if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
487 /* 334: Continue authentication request */
488 if (PEAR::isError($error = $this->_parseResponse(334))) {
489 /* 503: Error: already authenticated */
490 if ($this->_code === 503) {
496 $challenge = base64_decode($this->_arguments[0]);
497 $digest = &Auth_SASL::factory('digestmd5');
498 $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
499 $this->host, "smtp"));
501 if (PEAR::isError($error = $this->_put($auth_str))) {
504 /* 334: Continue authentication request */
505 if (PEAR::isError($error = $this->_parseResponse(334))) {
509 /* We don't use the protocol's third step because SMTP doesn't
510 * allow subsequent authentication, so we just silently ignore
512 if (PEAR::isError($error = $this->_put(' '))) {
515 /* 235: Authentication successful */
516 if (PEAR::isError($error = $this->_parseResponse(235))) {
522 * Authenticates the user using the CRAM-MD5 method.
524 * @param string The userid to authenticate as.
525 * @param string The password to authenticate with.
527 * @return mixed Returns a PEAR_Error with an error message on any
528 * kind of failure, or true on success.
532 function _authCRAM_MD5($uid, $pwd)
534 if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
537 /* 334: Continue authentication request */
538 if (PEAR::isError($error = $this->_parseResponse(334))) {
539 /* 503: Error: already authenticated */
540 if ($this->_code === 503) {
546 $challenge = base64_decode($this->_arguments[0]);
547 $cram = &Auth_SASL::factory('crammd5');
548 $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
550 if (PEAR::isError($error = $this->_put($auth_str))) {
554 /* 235: Authentication successful */
555 if (PEAR::isError($error = $this->_parseResponse(235))) {
561 * Authenticates the user using the LOGIN method.
563 * @param string The userid to authenticate as.
564 * @param string The password to authenticate with.
566 * @return mixed Returns a PEAR_Error with an error message on any
567 * kind of failure, or true on success.
571 function _authLogin($uid, $pwd)
573 if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
576 /* 334: Continue authentication request */
577 if (PEAR::isError($error = $this->_parseResponse(334))) {
578 /* 503: Error: already authenticated */
579 if ($this->_code === 503) {
585 if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
588 /* 334: Continue authentication request */
589 if (PEAR::isError($error = $this->_parseResponse(334))) {
593 if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
597 /* 235: Authentication successful */
598 if (PEAR::isError($error = $this->_parseResponse(235))) {
606 * Authenticates the user using the PLAIN method.
608 * @param string The userid to authenticate as.
609 * @param string The password to authenticate with.
611 * @return mixed Returns a PEAR_Error with an error message on any
612 * kind of failure, or true on success.
616 function _authPlain($uid, $pwd)
618 if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
621 /* 334: Continue authentication request */
622 if (PEAR::isError($error = $this->_parseResponse(334))) {
623 /* 503: Error: already authenticated */
624 if ($this->_code === 503) {
630 $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
632 if (PEAR::isError($error = $this->_put($auth_str))) {
636 /* 235: Authentication successful */
637 if (PEAR::isError($error = $this->_parseResponse(235))) {
645 * Send the HELO command.
647 * @param string The domain name to say we are.
649 * @return mixed Returns a PEAR_Error with an error message on any
650 * kind of failure, or true on success.
654 function helo($domain)
656 if (PEAR::isError($error = $this->_put('HELO', $domain))) {
659 if (PEAR::isError($error = $this->_parseResponse(250))) {
667 * Send the MAIL FROM: command.
669 * @param string The sender (reverse path) to set.
671 * @param array optional arguments. Currently supported:
672 * verp boolean or string. If true or string
673 * verp is enabled. If string the characters
674 * are considered verp separators.
676 * @return mixed Returns a PEAR_Error with an error message on any
677 * kind of failure, or true on success.
681 function mailFrom($sender, $args = array())
685 if (isset($args['verp'])) {
687 if ($args['verp'] === true) {
690 /* XVERP=something */
691 } elseif (trim($args['verp'])) {
692 $argstr .= ' XVERP=' . $args['verp'];
696 if (PEAR::isError($error = $this->_put('MAIL', "FROM:<$sender>$argstr"))) {
699 if (PEAR::isError($error = $this->_parseResponse(250))) {
707 * Send the RCPT TO: command.
709 * @param string The recipient (forward path) to add.
711 * @return mixed Returns a PEAR_Error with an error message on any
712 * kind of failure, or true on success.
716 function rcptTo($recipient)
718 if (PEAR::isError($error = $this->_put('RCPT', "TO:<$recipient>"))) {
721 if (PEAR::isError($error = $this->_parseResponse(array(250, 251)))) {
729 * Quote the data so that it meets SMTP standards.
731 * This is provided as a separate public function to facilitate
732 * easier overloading for the cases where it is desirable to
733 * customize the quoting behavior.
735 * @param string $data The message text to quote. The string must be passed
736 * by reference, and the text will be modified in place.
741 function quotedata(&$data)
743 /* Change Unix (\n) and Mac (\r) linefeeds into
744 * Internet-standard CRLF (\r\n) linefeeds. */
745 $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
747 /* Because a single leading period (.) signifies an end to the
748 * data, legitimate leading periods need to be "doubled"
750 $data = str_replace("\n.", "\n..", $data);
754 * Send the DATA command.
756 * @param string $data The message body to send.
758 * @return mixed Returns a PEAR_Error with an error message on any
759 * kind of failure, or true on success.
763 function data(&$data)
765 /* RFC 1870, section 3, subsection 3 states "a value of zero
766 * indicates that no fixed maximum message size is in force".
767 * Furthermore, it says that if "the parameter is omitted no
768 * information is conveyed about the server's fixed maximum
770 if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
771 if (strlen($data) >= $this->_esmtp['SIZE']) {
773 return PEAR::raiseError('Message size excedes the server limit');
777 /* Quote the data based on the SMTP standards. */
778 $this->quotedata($data);
780 if (PEAR::isError($error = $this->_put('DATA'))) {
783 if (PEAR::isError($error = $this->_parseResponse(354))) {
787 $data .= "\r\n.\r\n";
788 if (PEAR::isError($result = $this->_send($data))) {
791 if (PEAR::isError($error = $this->_parseResponse(250))) {
799 * Send the SEND FROM: command.
801 * @param string The reverse path to send.
803 * @return mixed Returns a PEAR_Error with an error message on any
804 * kind of failure, or true on success.
808 function sendFrom($path)
810 if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
813 if (PEAR::isError($error = $this->_parseResponse(250))) {
821 * Backwards-compatibility wrapper for sendFrom().
823 * @param string The reverse path to send.
825 * @return mixed Returns a PEAR_Error with an error message on any
826 * kind of failure, or true on success.
832 function send_from($path)
834 return sendFrom($path);
838 * Send the SOML FROM: command.
840 * @param string The reverse path to send.
842 * @return mixed Returns a PEAR_Error with an error message on any
843 * kind of failure, or true on success.
847 function somlFrom($path)
849 if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
852 if (PEAR::isError($error = $this->_parseResponse(250))) {
860 * Backwards-compatibility wrapper for somlFrom().
862 * @param string The reverse path to send.
864 * @return mixed Returns a PEAR_Error with an error message on any
865 * kind of failure, or true on success.
871 function soml_from($path)
873 return somlFrom($path);
877 * Send the SAML FROM: command.
879 * @param string The reverse path to send.
881 * @return mixed Returns a PEAR_Error with an error message on any
882 * kind of failure, or true on success.
886 function samlFrom($path)
888 if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
891 if (PEAR::isError($error = $this->_parseResponse(250))) {
899 * Backwards-compatibility wrapper for samlFrom().
901 * @param string The reverse path to send.
903 * @return mixed Returns a PEAR_Error with an error message on any
904 * kind of failure, or true on success.
910 function saml_from($path)
912 return samlFrom($path);
916 * Send the RSET command.
918 * @return mixed Returns a PEAR_Error with an error message on any
919 * kind of failure, or true on success.
925 if (PEAR::isError($error = $this->_put('RSET'))) {
928 if (PEAR::isError($error = $this->_parseResponse(250))) {
936 * Send the VRFY command.
938 * @param string The string to verify
940 * @return mixed Returns a PEAR_Error with an error message on any
941 * kind of failure, or true on success.
945 function vrfy($string)
947 /* Note: 251 is also a valid response code */
948 if (PEAR::isError($error = $this->_put('VRFY', $string))) {
951 if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
959 * Send the NOOP command.
961 * @return mixed Returns a PEAR_Error with an error message on any
962 * kind of failure, or true on success.
968 if (PEAR::isError($error = $this->_put('NOOP'))) {
971 if (PEAR::isError($error = $this->_parseResponse(250))) {
979 * Backwards-compatibility method. identifySender()'s functionality is
980 * now handled internally.
982 * @return boolean This method always return true.
987 function identifySender()