Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\Mailer\Transport\Smtp;
13
 
14
use Psr\EventDispatcher\EventDispatcherInterface;
15
use Psr\Log\LoggerInterface;
16
use Symfony\Component\Mailer\Exception\TransportException;
17
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
18
use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface;
19
use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
20
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
21
 
22
/**
23
 * Sends Emails over SMTP with ESMTP support.
24
 *
25
 * @author Fabien Potencier <fabien@symfony.com>
26
 * @author Chris Corbyn
27
 */
28
class EsmtpTransport extends SmtpTransport
29
{
30
    private array $authenticators = [];
31
    private string $username = '';
32
    private string $password = '';
33
    private array $capabilities;
34
 
35
    public function __construct(string $host = 'localhost', int $port = 0, bool $tls = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null, AbstractStream $stream = null)
36
    {
37
        parent::__construct($stream, $dispatcher, $logger);
38
 
39
        // order is important here (roughly most secure and popular first)
40
        $this->authenticators = [
41
            new Auth\CramMd5Authenticator(),
42
            new Auth\LoginAuthenticator(),
43
            new Auth\PlainAuthenticator(),
44
            new Auth\XOAuth2Authenticator(),
45
        ];
46
 
47
        /** @var SocketStream $stream */
48
        $stream = $this->getStream();
49
 
50
        if (null === $tls) {
51
            if (465 === $port) {
52
                $tls = true;
53
            } else {
54
                $tls = \defined('OPENSSL_VERSION_NUMBER') && 0 === $port && 'localhost' !== $host;
55
            }
56
        }
57
        if (!$tls) {
58
            $stream->disableTls();
59
        }
60
        if (0 === $port) {
61
            $port = $tls ? 465 : 25;
62
        }
63
 
64
        $stream->setHost($host);
65
        $stream->setPort($port);
66
    }
67
 
68
    /**
69
     * @return $this
70
     */
71
    public function setUsername(string $username): static
72
    {
73
        $this->username = $username;
74
 
75
        return $this;
76
    }
77
 
78
    public function getUsername(): string
79
    {
80
        return $this->username;
81
    }
82
 
83
    /**
84
     * @return $this
85
     */
86
    public function setPassword(#[\SensitiveParameter] string $password): static
87
    {
88
        $this->password = $password;
89
 
90
        return $this;
91
    }
92
 
93
    public function getPassword(): string
94
    {
95
        return $this->password;
96
    }
97
 
98
    public function addAuthenticator(AuthenticatorInterface $authenticator): void
99
    {
100
        $this->authenticators[] = $authenticator;
101
    }
102
 
103
    public function executeCommand(string $command, array $codes): string
104
    {
105
        return [250] === $codes && str_starts_with($command, 'HELO ') ? $this->doEhloCommand() : parent::executeCommand($command, $codes);
106
    }
107
 
108
    final protected function getCapabilities(): array
109
    {
110
        return $this->capabilities;
111
    }
112
 
113
    private function doEhloCommand(): string
114
    {
115
        try {
116
            $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]);
117
        } catch (TransportExceptionInterface $e) {
118
            try {
119
                return parent::executeCommand(sprintf("HELO %s\r\n", $this->getLocalDomain()), [250]);
120
            } catch (TransportExceptionInterface $ex) {
121
                if (!$ex->getCode()) {
122
                    throw $e;
123
                }
124
 
125
                throw $ex;
126
            }
127
        }
128
 
129
        $this->capabilities = $this->parseCapabilities($response);
130
 
131
        /** @var SocketStream $stream */
132
        $stream = $this->getStream();
133
        // WARNING: !$stream->isTLS() is right, 100% sure :)
134
        // if you think that the ! should be removed, read the code again
135
        // if doing so "fixes" your issue then it probably means your SMTP server behaves incorrectly or is wrongly configured
136
        if (!$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $this->capabilities)) {
137
            $this->executeCommand("STARTTLS\r\n", [220]);
138
 
139
            if (!$stream->startTLS()) {
140
                throw new TransportException('Unable to connect with STARTTLS.');
141
            }
142
 
143
            $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]);
144
            $this->capabilities = $this->parseCapabilities($response);
145
        }
146
 
147
        if (\array_key_exists('AUTH', $this->capabilities)) {
148
            $this->handleAuth($this->capabilities['AUTH']);
149
        }
150
 
151
        return $response;
152
    }
153
 
154
    private function parseCapabilities(string $ehloResponse): array
155
    {
156
        $capabilities = [];
157
        $lines = explode("\r\n", trim($ehloResponse));
158
        array_shift($lines);
159
        foreach ($lines as $line) {
160
            if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) {
161
                $value = strtoupper(ltrim($matches[2], ' ='));
162
                $capabilities[strtoupper($matches[1])] = $value ? explode(' ', $value) : [];
163
            }
164
        }
165
 
166
        return $capabilities;
167
    }
168
 
169
    private function handleAuth(array $modes): void
170
    {
171
        if (!$this->username) {
172
            return;
173
        }
174
 
175
        $authNames = [];
176
        $errors = [];
177
        $modes = array_map('strtolower', $modes);
178
        foreach ($this->authenticators as $authenticator) {
179
            if (!\in_array(strtolower($authenticator->getAuthKeyword()), $modes, true)) {
180
                continue;
181
            }
182
 
183
            $code = null;
184
            $authNames[] = $authenticator->getAuthKeyword();
185
            try {
186
                $authenticator->authenticate($this);
187
 
188
                return;
189
            } catch (TransportExceptionInterface $e) {
190
                $code = $e->getCode();
191
 
192
                try {
193
                    $this->executeCommand("RSET\r\n", [250]);
194
                } catch (TransportExceptionInterface) {
195
                    // ignore this exception as it probably means that the server error was final
196
                }
197
 
198
                // keep the error message, but tries the other authenticators
199
                $errors[$authenticator->getAuthKeyword()] = $e->getMessage();
200
            }
201
        }
202
 
203
        if (!$authNames) {
204
            throw new TransportException(sprintf('Failed to find an authenticator supported by the SMTP server, which currently supports: "%s".', implode('", "', $modes)), $code ?: 504);
205
        }
206
 
207
        $message = sprintf('Failed to authenticate on SMTP server with username "%s" using the following authenticators: "%s".', $this->username, implode('", "', $authNames));
208
        foreach ($errors as $name => $error) {
209
            $message .= sprintf(' Authenticator "%s" returned "%s".', $name, $error);
210
        }
211
 
212
        throw new TransportException($message, $code ?: 535);
213
    }
214
}