Overview

Packages

  • Mail

Classes

  • Horde_Mail
  • Horde_Mail_Exception
  • Horde_Mail_Rfc822
  • Horde_Mail_Rfc822_Address
  • Horde_Mail_Rfc822_Group
  • Horde_Mail_Rfc822_Object
  • Horde_Mail_Transport
  • Horde_Mail_Transport_Mail
  • Horde_Mail_Transport_Mock
  • Horde_Mail_Transport_Null
  • Horde_Mail_Transport_Sendmail
  • Horde_Mail_Transport_Smtp
  • Horde_Mail_Transport_Smtpmx
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * SMTP implementation.
  4:  * Requires the Net_SMTP class.
  5:  *
  6:  * LICENSE:
  7:  *
  8:  * Copyright (c) 2010, Chuck Hagenbuch
  9:  * All rights reserved.
 10:  *
 11:  * Redistribution and use in source and binary forms, with or without
 12:  * modification, are permitted provided that the following conditions
 13:  * are met:
 14:  *
 15:  * o Redistributions of source code must retain the above copyright
 16:  *   notice, this list of conditions and the following disclaimer.
 17:  * o Redistributions in binary form must reproduce the above copyright
 18:  *   notice, this list of conditions and the following disclaimer in the
 19:  *   documentation and/or other materials provided with the distribution.
 20:  * o The names of the authors may not be used to endorse or promote
 21:  *   products derived from this software without specific prior written
 22:  *   permission.
 23:  *
 24:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 25:  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 26:  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 27:  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 28:  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 29:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 30:  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 31:  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 32:  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 33:  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 34:  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 35:  *
 36:  * @category  Horde
 37:  * @package   Mail
 38:  * @author    Jon Parise <jon@php.net>
 39:  * @author    Chuck Hagenbuch <chuck@horde.org>
 40:  * @copyright 2010 Chuck Hagenbuch
 41:  * @license   http://www.horde.org/licenses/bsd New BSD License
 42:  */
 43: 
 44: /**
 45:  * SMTP implementation.
 46:  *
 47:  * @category Horde
 48:  * @package  Mail
 49:  */
 50: class Horde_Mail_Transport_Smtp extends Horde_Mail_Transport
 51: {
 52:     /* Error: Failed to create a Net_SMTP object */
 53:     const ERROR_CREATE = 10000;
 54: 
 55:     /* Error: Failed to connect to SMTP server */
 56:     const ERROR_CONNECT = 10001;
 57: 
 58:     /* Error: SMTP authentication failure */
 59:     const ERROR_AUTH = 10002;
 60: 
 61:     /* Error: No From: address has been provided */
 62:     const ERROR_FROM = 10003;
 63: 
 64:     /* Error: Failed to set sender */
 65:     const ERROR_SENDER = 10004;
 66: 
 67:     /* Error: Failed to add recipient */
 68:     const ERROR_RECIPIENT = 10005;
 69: 
 70:     /* Error: Failed to send data */
 71:     const ERROR_DATA = 10006;
 72: 
 73:     /**
 74:      * The SMTP greeting.
 75:      *
 76:      * @var string
 77:      */
 78:     public $greeting = null;
 79: 
 80:     /**
 81:      * The SMTP queued response.
 82:      *
 83:      * @var string
 84:      */
 85:     public $queuedAs = null;
 86: 
 87:     /**
 88:      * SMTP connection object.
 89:      *
 90:      * @var Net_SMTP
 91:      */
 92:     protected $_smtp = null;
 93: 
 94:     /**
 95:      * The list of service extension parameters to pass to the Net_SMTP
 96:      * mailFrom() command.
 97:      *
 98:      * @var array
 99:      */
100:     protected $_extparams = array();
101: 
102:     /**
103:      * Constructor.
104:      *
105:      * @param array $params  Additional parameters:
106:      *   - auth: (mixed) SMTP authentication.
107:      *           This value may be set to true, false or the name of a
108:      *           specific authentication method. If the value is set to true,
109:      *           the Net_SMTP package will attempt to use the best
110:      *           authentication method advertised by the remote SMTP server.
111:      *           DEFAULT: false.
112:      *   - debug: (boolean) Activate SMTP debug mode?
113:      *            DEFAULT: false
114:      *   - host: (string) The server to connect to.
115:      *           DEFAULT: localhost
116:      *   - localhost: (string) Hostname or domain that will be sent to the
117:      *                remote SMTP server in the HELO / EHLO message.
118:      *                DEFAULT: localhost
119:      *   - password: (string) The password to use for SMTP auth.
120:      *               DEFAULT: NONE
121:      *   - persist: (boolean) Should the SMTP connection persist?
122:      *              DEFAULT: false
123:      *   - pipelining: (boolean) Use SMTP command pipelining.
124:      *                 Use SMTP command pipelining (specified in RFC 2920) if
125:      *                 the SMTP server supports it. This speeds up delivery
126:      *                 over high-latency connections.
127:      *                 DEFAULT: false (use default value from Net_SMTP)
128:      *   - port: (integer) The port to connect to.
129:      *           DEFAULT: 25
130:      *   - timeout: (integer) The SMTP connection timeout.
131:      *              DEFAULT: NONE
132:      *   - username: (string) The username to use for SMTP auth.
133:      *               DEFAULT: NONE
134:      */
135:     public function __construct(array $params = array())
136:     {
137:         $this->_params = array_merge(array(
138:             'auth' => false,
139:             'debug' => false,
140:             'host' => 'localhost',
141:             'localhost' => 'localhost',
142:             'password' => '',
143:             'persist' => false,
144:             'pipelining' => false,
145:             'port' => 25,
146:             'timeout' => null,
147:             'username' => ''
148:         ), $params);
149: 
150:         /* Destructor implementation to ensure that we disconnect from any
151:          * potentially-alive persistent SMTP connections. */
152:         register_shutdown_function(array($this, 'disconnect'));
153: 
154:         /* SMTP requires CRLF line endings. */
155:         $this->sep = "\r\n";
156:     }
157: 
158:     /**
159:      * Send a message.
160:      *
161:      * @param mixed $recipients  Either a comma-seperated list of recipients
162:      *                           (RFC822 compliant), or an array of
163:      *                           recipients, each RFC822 valid. This may
164:      *                           contain recipients not specified in the
165:      *                           headers, for Bcc:, resending messages, etc.
166:      * @param array $headers     The headers to send with the mail, in an
167:      *                           associative array, where the array key is the
168:      *                           header name (ie, 'Subject'), and the array
169:      *                           value is the header value (ie, 'test'). The
170:      *                           header produced from those values would be
171:      *                           'Subject: test'.
172:      *                           If the '_raw' key exists, the value of this
173:      *                           key will be used as the exact text for
174:      *                           sending the message.
175:      * @param mixed $body        The full text of the message body, including
176:      *                           any Mime parts, etc. Either a string or a
177:      *                           stream resource.
178:      *
179:      * @throws Horde_Mail_Exception
180:      */
181:     public function send($recipients, array $headers, $body)
182:     {
183:         /* If we don't already have an SMTP object, create one. */
184:         $this->getSMTPObject();
185: 
186:         $headers = $this->_sanitizeHeaders($headers);
187: 
188:         try {
189:             list($from, $textHeaders) = $this->prepareHeaders($headers);
190:         } catch (Horde_Mail_Exception $e) {
191:             $this->_smtp->rset();
192:             throw $e;
193:         }
194: 
195:         /* Since few MTAs are going to allow this header to be forged unless
196:          * it's in the MAIL FROM: exchange, we'll use Return-Path instead of
197:          * From: if it's set. */
198:         foreach (array_keys($headers) as $hdr) {
199:             if (strcasecmp($hdr, 'Return-Path') === 0) {
200:                 $from = $headers[$hdr];
201:                 break;
202:             }
203:         }
204: 
205:         if (!strlen($from)) {
206:             $this->_smtp->rset();
207:             throw new Horde_Mail_Exception('No From: address has been provided', self::ERROR_FROM);
208:         }
209: 
210:         $params = '';
211:         foreach ($this->_extparams as $key => $val) {
212:             $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
213:         }
214: 
215:         $res = $this->_smtp->mailFrom($from, ltrim($params));
216:         if ($res instanceof PEAR_Error) {
217:             $this->_error("Failed to set sender: $from", $res, self::ERROR_SENDER);
218:         }
219: 
220:         try {
221:             $recipients = $this->parseRecipients($recipients);
222:         } catch (Horde_Mail_Exception $e) {
223:             $this->_smtp->rset();
224:             throw $e;
225:         }
226: 
227:         foreach ($recipients as $recipient) {
228:             $res = $this->_smtp->rcptTo($recipient);
229:             if ($res instanceof PEAR_Error) {
230:                 $this->_error("Failed to add recipient: $recipient", $res, self::ERROR_RECIPIENT);
231:             }
232:         }
233: 
234:         /* Send the message's headers and the body as SMTP data. Net_SMTP does
235:          * the necessary EOL conversions. */
236:         $res = $this->_smtp->data($body, $textHeaders);
237:         list(,$args) = $this->_smtp->getResponse();
238: 
239:         if (preg_match("/Ok: queued as (.*)/", $args, $queued)) {
240:             $this->queuedAs = $queued[1];
241:         }
242: 
243:         /* We need the greeting; from it we can extract the authorative name
244:          * of the mail server we've really connected to. Ideal if we're
245:          * connecting to a round-robin of relay servers and need to track
246:          * which exact one took the email */
247:         $this->greeting = $this->_smtp->getGreeting();
248: 
249:         if ($res instanceof PEAR_Error) {
250:             $this->_error('Failed to send data', $res, self::ERROR_DATA);
251:         }
252: 
253:         /* If persistent connections are disabled, destroy our SMTP object. */
254:         if ($this->_params['persist']) {
255:             $this->disconnect();
256:         }
257:     }
258: 
259:     /**
260:      * Connect to the SMTP server by instantiating a Net_SMTP object.
261:      *
262:      * @return Net_SMTP  The SMTP object.
263:      * @throws Horde_Mail_Exception
264:      */
265:     public function getSMTPObject()
266:     {
267:         if ($this->_smtp) {
268:             return $this->_smtp;
269:         }
270: 
271:         $this->_smtp = new Net_SMTP(
272:             $this->_params['host'],
273:             $this->_params['port'],
274:             $this->_params['localhost']
275:         );
276: 
277:         /* If we still don't have an SMTP object at this point, fail. */
278:         if (!($this->_smtp instanceof Net_SMTP)) {
279:             throw new Horde_Mail_Exception('Failed to create a Net_SMTP object', self::ERROR_CREATE);
280:         }
281: 
282:         /* Configure the SMTP connection. */
283:         if ($this->_params['debug']) {
284:             $this->_smtp->setDebug(true);
285:         }
286: 
287:         /* Attempt to connect to the configured SMTP server. */
288:         $res = $this->_smtp->connect($this->_params['timeout']);
289:         if ($res instanceof PEAR_Error) {
290:             $this->_error('Failed to connect to ' . $this->_params['host'] . ':' . $this->_params['port'], $res, self::ERROR_CONNECT);
291:         }
292: 
293:         /* Attempt to authenticate if authentication has been enabled. */
294:         if ($this->_params['auth']) {
295:             $method = is_string($this->_params['auth'])
296:                 ? $this->_params['auth']
297:                 : '';
298: 
299:             $res = $this->_smtp->auth($this->_params['username'], $this->_params['password'], $method);
300:             if ($res instanceof PEAR_Error) {
301:                 $this->_error("$method authentication failure", $res, self::ERROR_AUTH);
302:             }
303:         }
304: 
305:         return $this->_smtp;
306:     }
307: 
308:     /**
309:      * Add parameter associated with a SMTP service extension.
310:      *
311:      * @param string $keyword  Extension keyword.
312:      * @param string $value    Any value the keyword needs.
313:      */
314:     public function addServiceExtensionParameter($keyword, $value = null)
315:     {
316:         $this->_extparams[$keyword] = $value;
317:     }
318: 
319:     /**
320:      * Disconnect and destroy the current SMTP connection.
321:      *
322:      * @return boolean True if the SMTP connection no longer exists.
323:      */
324:     public function disconnect()
325:     {
326:         /* If we have an SMTP object, disconnect and destroy it. */
327:         if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
328:             $this->_smtp = null;
329:         }
330: 
331:         /* We are disconnected if we no longer have an SMTP object. */
332:         return ($this->_smtp === null);
333:     }
334: 
335:     /**
336:      * Build a standardized string describing the current SMTP error.
337:      *
338:      * @param string $text       Custom string describing the error context.
339:      * @param PEAR_Error $error  PEAR_Error object.
340:      * @param integer $e_code    Error code.
341:      *
342:      * @throws Horde_Mail_Exception
343:      */
344:     protected function _error($text, $error, $e_code)
345:     {
346:         /* Split the SMTP response into a code and a response string. */
347:         list($code, $response) = $this->_smtp->getResponse();
348: 
349:         /* Abort current SMTP transaction. */
350:         $this->_smtp->rset();
351: 
352:         /* Build our standardized error string. */
353:         throw new Horde_Mail_Exception($text . ' [SMTP: ' . $error->getMessage() . " (code: $code, response: $response)]", $e_code);
354:     }
355: }
356: 
API documentation generated by ApiGen