Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / mail / SMTPMailSender.class.php
CommitLineData
158bd3ca
TD
1<?php
2namespace wcf\system\mail;
3use wcf\system\exception\SystemException;
4use wcf\system\io\RemoteFile;
dd1ff120 5use wcf\util\StringUtil;
158bd3ca
TD
6
7/**
8 * Sends a Mail with a connection to a smtp server.
9 *
4a92e525 10 * @author Tim Duesterhus, Alexander Ebert
ca4ba303 11 * @copyright 2001-2014 WoltLab GmbH
158bd3ca
TD
12 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
13 * @package com.woltlab.wcf
14 * @subpackage data.mail
9f959ced 15 * @category Community Framework
158bd3ca
TD
16 */
17class SMTPMailSender extends MailSender {
3a674fcc
MW
18 /**
19 * smtp connection
0ad90fc3 20 * @var \wcf\system\io\RemoteFile
3a674fcc 21 */
158bd3ca 22 protected $connection = null;
3a674fcc
MW
23
24 /**
25 * last received status code
06355ec3 26 * @var string
3a674fcc 27 */
158bd3ca 28 protected $statusCode = '';
3a674fcc
MW
29
30 /**
31 * last received status message
06355ec3 32 * @var string
3a674fcc 33 */
158bd3ca 34 protected $statusMsg = '';
3a674fcc
MW
35
36 /**
37 * mail recipients
06355ec3 38 * @var array
3a674fcc
MW
39 */
40 protected $recipients = array();
158bd3ca
TD
41
42 /**
43 * Creates a new SMTPMailSender object.
44 */
45 public function __construct() {
3a674fcc 46 Mail::$lineEnding = "\r\n";
158bd3ca
TD
47 }
48
49 /**
50 * Destroys the SMTPMailSender object.
51 */
52 public function __destruct() {
53 $this->disconnect();
54 }
9f959ced 55
158bd3ca
TD
56 /**
57 * Connects to the smtp-server
58 */
59 protected function connect() {
60 // connect
61 $this->connection = new RemoteFile(MAIL_SMTP_HOST, MAIL_SMTP_PORT);
62 $this->getSMTPStatus();
63 if ($this->statusCode != 220) {
4fe0b42b 64 throw new SystemException($this->formatError("can not connect to '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."'"));
158bd3ca
TD
65 }
66
e2b37031
AE
67 $host = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : '';
68 if (empty($host)) {
69 $host = gethostname();
70 if ($host === false) {
71 $host = 'localhost';
72 }
73 }
74
158bd3ca 75 // send ehlo
e2b37031 76 $this->write('EHLO '.$host);
4a92e525
TD
77 $extensions = explode(Mail::$lineEnding, $this->read());
78 $this->getSMTPStatus(array_shift($extensions));
158bd3ca 79 if ($this->statusCode == 250) {
4a92e525
TD
80 $extensions = array_map(function($element) {
81 return strtolower(substr($element, 4));
82 }, $extensions);
83
84 if ($this->connection->hasTLSSupport() && in_array('starttls', $extensions)) {
85 $this->write('STARTTLS');
86 $this->getSMTPStatus();
87
88 if ($this->statusCode != 220) {
89 throw new SystemException($this->formatError("cannot enable STARTTLS, though '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."' advertised it"));
90 }
91
92 if (!$this->connection->setTLS(true)) {
93 throw new SystemException('enabling TLS failed');
94 }
95
96 // repeat EHLO
97 $this->write('EHLO '.$host);
98 $extensions = explode(Mail::$lineEnding, $this->read());
99 $this->getSMTPStatus(array_shift($extensions));
100
101 if ($this->statusCode != 250) {
102 throw new SystemException($this->formatError("could not EHLO after enabling STARTTLS at '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."'"));
103 }
104 }
105
158bd3ca
TD
106 // do authentication
107 if (MAIL_SMTP_USER != '' || MAIL_SMTP_PASSWORD != '') {
108 $this->auth();
109 }
110 }
111 else {
112 // send helo
e2b37031 113 $this->write('HELO '.$host);
158bd3ca
TD
114 $this->getSMTPStatus();
115 if ($this->statusCode != 250) {
4fe0b42b 116 throw new SystemException($this->formatError("can not connect to '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."'"));
158bd3ca
TD
117 }
118 }
119 }
120
121 /**
122 * Formats a smtp error message.
123 *
124 * @param string $message
125 * @return string
126 */
127 protected function formatError($message) {
128 return $message.': '.$this->statusMsg.' ('.$this->statusCode.')';
129 }
130
131 /**
a17de04e 132 * Does the authentification of the client on the server
158bd3ca
TD
133 */
134 protected function auth() {
a17de04e 135 // init authentication
158bd3ca
TD
136 $this->write('AUTH LOGIN');
137 $this->getSMTPStatus();
138
139 // checks if auth is supported
140 if ($this->statusCode != 334) {
4fe0b42b 141 throw new SystemException($this->formatError("smtp mail server '".MAIL_SMTP_HOST.":".MAIL_SMTP_PORT."' does not support user authentication"));
158bd3ca
TD
142 }
143
144 // sending user information to smtp-server
145 $this->write(base64_encode(MAIL_SMTP_USER));
146 $this->getSMTPStatus();
147 if ($this->statusCode != 334) {
4fe0b42b 148 throw new SystemException($this->formatError("unknown smtp user '".MAIL_SMTP_USER."'"));
158bd3ca 149 }
a17de04e 150
158bd3ca
TD
151 $this->write(base64_encode(MAIL_SMTP_PASSWORD));
152 $this->getSMTPStatus();
153 if ($this->statusCode != 235) {
4fe0b42b 154 throw new SystemException($this->formatError("invalid password for smtp user '".MAIL_SMTP_USER."'"));
158bd3ca
TD
155 }
156 }
157
158 /**
0ad90fc3 159 * @see \wcf\system\mail\MailSender::sendMail()
158bd3ca
TD
160 */
161 public function sendMail(Mail $mail) {
162 $this->recipients = array();
15fa2802
MS
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());
158bd3ca
TD
166
167 // apply connection
168 if ($this->connection === null) {
169 $this->connect();
170 }
171
172 // send mail
173 $this->write('MAIL FROM:<'.$mail->getFrom().'>');
174 $this->getSMTPStatus();
175 if ($this->statusCode != 250) {
5e43e307 176 $this->abort();
4fe0b42b 177 throw new SystemException($this->formatError("wrong from format '".$mail->getFrom()."'"));
158bd3ca
TD
178 }
179
180 // recipients
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) {
5e43e307 187 $this->abort();
4fe0b42b 188 throw new SystemException($this->formatError("wrong recipient format '".$recipient."'"));
158bd3ca
TD
189 }
190 continue;
191 }
192 $recipientCounter++;
193 }
194 if (!$recipientCounter) {
5e43e307 195 $this->abort();
158bd3ca
TD
196 return;
197 }
198
199 // data
200 $this->write("DATA");
201 $this->getSMTPStatus();
202 if ($this->statusCode != 354 && $this->statusCode != 250) {
5e43e307 203 $this->abort();
4fe0b42b 204 throw new SystemException($this->formatError("smtp error"));
158bd3ca 205 }
a17de04e 206
e2b37031
AE
207 $serverName = (isset($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : '';
208 if (empty($serverName)) {
209 $serverName = gethostname();
210 if ($serverName === false) {
211 $serverName = 'localhost';
212 }
213 }
214
158bd3ca 215 $header =
3a674fcc
MW
216 "Date: ".gmdate('r').Mail::$lineEnding
217 ."To: ".$mail->getToString().Mail::$lineEnding
e2b37031 218 ."Message-ID: <".md5(uniqid())."@".$serverName.">".Mail::$lineEnding
3a674fcc 219 ."Subject: ".Mail::encodeMIMEHeader($mail->getSubject()).Mail::$lineEnding
158bd3ca 220 .$mail->getHeader();
9f959ced 221
158bd3ca
TD
222 $this->write($header);
223 $this->write("");
dd1ff120
TD
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;
231 $this->write($line);
232 }
158bd3ca 233 $this->write(".");
a17de04e 234
158bd3ca
TD
235 $this->getSMTPStatus();
236 if ($this->statusCode != 250) {
5e43e307 237 $this->abort();
4fe0b42b 238 throw new SystemException($this->formatError("message sending failed"));
158bd3ca
TD
239 }
240 }
241
242 /**
243 * Disconnects the Client-Server connection
244 */
81c5087a 245 public function disconnect() {
158bd3ca
TD
246 if ($this->connection === null) {
247 return;
248 }
9f959ced 249
158bd3ca
TD
250 $this->write("QUIT");
251 $this->read();
252 $this->connection->close();
253 $this->connection = null;
254 }
255
256 /**
257 * Reads the Information wich the Server sends back.
258 *
39bea7dd 259 * @return string
158bd3ca
TD
260 */
261 protected function read() {
262 $result = '';
263 while ($read = $this->connection->gets()) {
264 $result .= $read;
265 if (substr($read, 3, 1) == " ") break;
266 }
4a92e525 267
92fd47d9 268 return $result;
158bd3ca
TD
269 }
270
5e43e307 271 /**
272 * Aborts the current process. This is needed in case a new mail should be
273 * sent after a exception has occured
274 */
275 protected function abort() {
276 $this->write("RSET");
277 $this->read(); // read response, but do not care about status here
278 }
279
158bd3ca
TD
280 /**
281 * Gets error code and message from a server message.
282 *
283 * @param string $data
284 */
285 protected function getSMTPStatus($data = null) {
286 if ($data === null) $data = $this->read();
287 $this->statusCode = intval(substr($data, 0, 3));
01bd2eff 288 $this->statusMsg = substr($data, 4);
158bd3ca
TD
289 }
290
291 /**
292 * Sends Information to the smtp-Server
293 *
294 * @param string $data
295 */
296 protected function write($data) {
3a674fcc 297 $this->connection->puts($data.Mail::$lineEnding);
158bd3ca 298 }
dcb3a44c 299}