2 namespace wcf\system\mail
;
3 use wcf\system\exception\SystemException
;
4 use wcf\system\io\RemoteFile
;
5 use wcf\util\StringUtil
;
8 * Sends a Mail with a connection to a smtp server.
10 * @author Tim Duesterhus, Alexander Ebert
11 * @copyright 2001-2014 WoltLab GmbH
12 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
13 * @package com.woltlab.wcf
14 * @subpackage data.mail
15 * @category Community Framework
17 class SMTPMailSender
extends MailSender
{
20 * @var \wcf\system\io\RemoteFile
22 protected $connection = null;
25 * last received status code
28 protected $statusCode = '';
31 * last received status message
34 protected $statusMsg = '';
40 protected $recipients = array();
43 * Creates a new SMTPMailSender object.
45 public function __construct() {
46 Mail
::$lineEnding = "\r\n";
50 * Destroys the SMTPMailSender object.
52 public function __destruct() {
57 * Connects to the smtp-server
59 protected function connect() {
61 $this->connection
= new RemoteFile(MAIL_SMTP_HOST
, MAIL_SMTP_PORT
);
62 $this->getSMTPStatus();
63 if ($this->statusCode
!= 220) {
64 throw new SystemException($this->formatError("can not connect to '".MAIL_SMTP_HOST
.":".MAIL_SMTP_PORT
."'"));
67 $host = (isset($_SERVER['HTTP_HOST'])) ?
$_SERVER['HTTP_HOST'] : '';
69 $host = gethostname();
70 if ($host === false) {
76 $this->write('EHLO '.$host);
77 $extensions = explode(Mail
::$lineEnding, $this->read());
78 $this->getSMTPStatus(array_shift($extensions));
79 if ($this->statusCode
== 250) {
80 $extensions = array_map(function($element) {
81 return strtolower(substr($element, 4));
84 if ($this->connection
->hasTLSSupport() && in_array('starttls', $extensions)) {
85 $this->write('STARTTLS');
86 $this->getSMTPStatus();
88 if ($this->statusCode
!= 220) {
89 throw new SystemException($this->formatError("cannot enable STARTTLS, though '".MAIL_SMTP_HOST
.":".MAIL_SMTP_PORT
."' advertised it"));
92 if (!$this->connection
->setTLS(true)) {
93 throw new SystemException('enabling TLS failed');
97 $this->write('EHLO '.$host);
98 $extensions = explode(Mail
::$lineEnding, $this->read());
99 $this->getSMTPStatus(array_shift($extensions));
101 if ($this->statusCode
!= 250) {
102 throw new SystemException($this->formatError("could not EHLO after enabling STARTTLS at '".MAIL_SMTP_HOST
.":".MAIL_SMTP_PORT
."'"));
107 if (MAIL_SMTP_USER
!= '' || MAIL_SMTP_PASSWORD
!= '') {
113 $this->write('HELO '.$host);
114 $this->getSMTPStatus();
115 if ($this->statusCode
!= 250) {
116 throw new SystemException($this->formatError("can not connect to '".MAIL_SMTP_HOST
.":".MAIL_SMTP_PORT
."'"));
122 * Formats a smtp error message.
124 * @param string $message
127 protected function formatError($message) {
128 return $message.': '.$this->statusMsg
.' ('.$this->statusCode
.')';
132 * Does the authentification of the client on the server
134 protected function auth() {
135 // init authentication
136 $this->write('AUTH LOGIN');
137 $this->getSMTPStatus();
139 // checks if auth is supported
140 if ($this->statusCode
!= 334) {
141 throw new SystemException($this->formatError("smtp mail server '".MAIL_SMTP_HOST
.":".MAIL_SMTP_PORT
."' does not support user authentication"));
144 // sending user information to smtp-server
145 $this->write(base64_encode(MAIL_SMTP_USER
));
146 $this->getSMTPStatus();
147 if ($this->statusCode
!= 334) {
148 throw new SystemException($this->formatError("unknown smtp user '".MAIL_SMTP_USER
."'"));
151 $this->write(base64_encode(MAIL_SMTP_PASSWORD
));
152 $this->getSMTPStatus();
153 if ($this->statusCode
!= 235) {
154 throw new SystemException($this->formatError("invalid password for smtp user '".MAIL_SMTP_USER
."'"));
159 * @see \wcf\system\mail\MailSender::sendMail()
161 public function sendMail(Mail
$mail) {
162 $this->recipients
= array();
163 if (count($mail->getTo()) > 0) $this->recipients
= $mail->getTo();
164 if (count($mail->getCC()) > 0) $this->recipients
= array_merge($this->recipients
, $mail->getCC());
165 if (count($mail->getBCC())> 0) $this->recipients
= array_merge($this->recipients
, $mail->getBCC());
168 if ($this->connection
=== null) {
173 $this->write('MAIL FROM:<'.$mail->getFrom().'>');
174 $this->getSMTPStatus();
175 if ($this->statusCode
!= 250) {
177 throw new SystemException($this->formatError("wrong from format '".$mail->getFrom()."'"));
181 $recipientCounter = 0;
182 foreach ($this->recipients
as $recipient) {
183 $this->write('RCPT TO:<'.$recipient.'>');
184 $this->getSMTPStatus();
185 if ($this->statusCode
!= 250 && $this->statusCode
!= 251) {
186 if ($this->statusCode
< 550) {
188 throw new SystemException($this->formatError("wrong recipient format '".$recipient."'"));
194 if (!$recipientCounter) {
200 $this->write("DATA");
201 $this->getSMTPStatus();
202 if ($this->statusCode
!= 354 && $this->statusCode
!= 250) {
204 throw new SystemException($this->formatError("smtp error"));
207 $serverName = (isset($_SERVER['SERVER_NAME'])) ?
$_SERVER['SERVER_NAME'] : '';
208 if (empty($serverName)) {
209 $serverName = gethostname();
210 if ($serverName === false) {
211 $serverName = 'localhost';
216 "Date: ".gmdate('r').Mail
::$lineEnding
217 ."To: ".$mail->getToString().Mail
::$lineEnding
218 ."Message-ID: <".md5(uniqid())."@".$serverName.">".Mail
::$lineEnding
219 ."Subject: ".Mail
::encodeMIMEHeader($mail->getSubject()).Mail
::$lineEnding
222 $this->write($header);
224 $lines = explode(Mail
::$lineEnding, $mail->getBody());
225 foreach ($lines as $line) {
226 // 4.5.2 Transparency
227 // o Before sending a line of mail text, the SMTP client checks the
228 // first character of the line. If it is a period, one additional
229 // period is inserted at the beginning of the line.
230 if (StringUtil
::startsWith($line, '.')) $line = '.'.$line;
235 $this->getSMTPStatus();
236 if ($this->statusCode
!= 250) {
238 throw new SystemException($this->formatError("message sending failed"));
243 * Disconnects the Client-Server connection
245 public function disconnect() {
246 if ($this->connection
=== null) {
250 $this->write("QUIT");
252 $this->connection
->close();
253 $this->connection
= null;
257 * Reads the Information wich the Server sends back.
261 protected function read() {
263 while ($read = $this->connection
->gets()) {
265 if (substr($read, 3, 1) == " ") break;
272 * Aborts the current process. This is needed in case a new mail should be
273 * sent after a exception has occured
275 protected function abort() {
276 $this->write("RSET");
277 $this->read(); // read response, but do not care about status here
281 * Gets error code and message from a server message.
283 * @param string $data
285 protected function getSMTPStatus($data = null) {
286 if ($data === null) $data = $this->read();
287 $this->statusCode
= intval(substr($data, 0, 3));
288 $this->statusMsg
= substr($data, 4);
292 * Sends Information to the smtp-Server
294 * @param string $data
296 protected function write($data) {
297 $this->connection
->puts($data.Mail
::$lineEnding);