1<?php
2
3/*
4 * This file is part of SwiftMailer.
5 * (c) 2004-2009 Chris Corbyn
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11/**
12 * Makes sure a connection to a POP3 host has been established prior to connecting to SMTP.
13 *
14 * @author     Chris Corbyn
15 */
16class Swift_Plugins_PopBeforeSmtpPlugin implements Swift_Events_TransportChangeListener, Swift_Plugins_Pop_Pop3Connection
17{
18    /** A delegate connection to use (mostly a test hook) */
19    private $connection;
20
21    /** Hostname of the POP3 server */
22    private $host;
23
24    /** Port number to connect on */
25    private $port;
26
27    /** Encryption type to use (if any) */
28    private $crypto;
29
30    /** Username to use (if any) */
31    private $username;
32
33    /** Password to use (if any) */
34    private $password;
35
36    /** Established connection via TCP socket */
37    private $socket;
38
39    /** Connect timeout in seconds */
40    private $timeout = 10;
41
42    /** SMTP Transport to bind to */
43    private $transport;
44
45    /**
46     * Create a new PopBeforeSmtpPlugin for $host and $port.
47     *
48     * @param string $host
49     * @param int    $port
50     * @param string $crypto as "tls" or "ssl"
51     */
52    public function __construct($host, $port = 110, $crypto = null)
53    {
54        $this->host = $host;
55        $this->port = $port;
56        $this->crypto = $crypto;
57    }
58
59    /**
60     * Set a Pop3Connection to delegate to instead of connecting directly.
61     *
62     * @param Swift_Plugins_Pop_Pop3Connection $connection
63     *
64     * @return $this
65     */
66    public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection)
67    {
68        $this->connection = $connection;
69
70        return $this;
71    }
72
73    /**
74     * Bind this plugin to a specific SMTP transport instance.
75     *
76     * @param Swift_Transport
77     */
78    public function bindSmtp(Swift_Transport $smtp)
79    {
80        $this->transport = $smtp;
81    }
82
83    /**
84     * Set the connection timeout in seconds (default 10).
85     *
86     * @param int $timeout
87     *
88     * @return $this
89     */
90    public function setTimeout($timeout)
91    {
92        $this->timeout = (int) $timeout;
93
94        return $this;
95    }
96
97    /**
98     * Set the username to use when connecting (if needed).
99     *
100     * @param string $username
101     *
102     * @return $this
103     */
104    public function setUsername($username)
105    {
106        $this->username = $username;
107
108        return $this;
109    }
110
111    /**
112     * Set the password to use when connecting (if needed).
113     *
114     * @param string $password
115     *
116     * @return $this
117     */
118    public function setPassword($password)
119    {
120        $this->password = $password;
121
122        return $this;
123    }
124
125    /**
126     * Connect to the POP3 host and authenticate.
127     *
128     * @throws Swift_Plugins_Pop_Pop3Exception if connection fails
129     */
130    public function connect()
131    {
132        if (isset($this->connection)) {
133            $this->connection->connect();
134        } else {
135            if (!isset($this->socket)) {
136                if (!$socket = fsockopen(
137                    $this->getHostString(), $this->port, $errno, $errstr, $this->timeout)) {
138                    throw new Swift_Plugins_Pop_Pop3Exception(
139                        sprintf('Failed to connect to POP3 host [%s]: %s', $this->host, $errstr)
140                    );
141                }
142                $this->socket = $socket;
143
144                if (false === $greeting = fgets($this->socket)) {
145                    throw new Swift_Plugins_Pop_Pop3Exception(
146                        sprintf('Failed to connect to POP3 host [%s]', trim($greeting))
147                    );
148                }
149
150                $this->assertOk($greeting);
151
152                if ($this->username) {
153                    $this->command(sprintf("USER %s\r\n", $this->username));
154                    $this->command(sprintf("PASS %s\r\n", $this->password));
155                }
156            }
157        }
158    }
159
160    /**
161     * Disconnect from the POP3 host.
162     */
163    public function disconnect()
164    {
165        if (isset($this->connection)) {
166            $this->connection->disconnect();
167        } else {
168            $this->command("QUIT\r\n");
169            if (!fclose($this->socket)) {
170                throw new Swift_Plugins_Pop_Pop3Exception(
171                    sprintf('POP3 host [%s] connection could not be stopped', $this->host)
172                );
173            }
174            $this->socket = null;
175        }
176    }
177
178    /**
179     * Invoked just before a Transport is started.
180     *
181     * @param Swift_Events_TransportChangeEvent $evt
182     */
183    public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt)
184    {
185        if (isset($this->transport)) {
186            if ($this->transport !== $evt->getTransport()) {
187                return;
188            }
189        }
190
191        $this->connect();
192        $this->disconnect();
193    }
194
195    /**
196     * Not used.
197     */
198    public function transportStarted(Swift_Events_TransportChangeEvent $evt)
199    {
200    }
201
202    /**
203     * Not used.
204     */
205    public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt)
206    {
207    }
208
209    /**
210     * Not used.
211     */
212    public function transportStopped(Swift_Events_TransportChangeEvent $evt)
213    {
214    }
215
216    private function command($command)
217    {
218        if (!fwrite($this->socket, $command)) {
219            throw new Swift_Plugins_Pop_Pop3Exception(
220                sprintf('Failed to write command [%s] to POP3 host', trim($command))
221            );
222        }
223
224        if (false === $response = fgets($this->socket)) {
225            throw new Swift_Plugins_Pop_Pop3Exception(
226                sprintf('Failed to read from POP3 host after command [%s]', trim($command))
227            );
228        }
229
230        $this->assertOk($response);
231
232        return $response;
233    }
234
235    private function assertOk($response)
236    {
237        if (substr($response, 0, 3) != '+OK') {
238            throw new Swift_Plugins_Pop_Pop3Exception(
239                sprintf('POP3 command failed [%s]', trim($response))
240            );
241        }
242    }
243
244    private function getHostString()
245    {
246        $host = $this->host;
247        switch (strtolower($this->crypto)) {
248            case 'ssl':
249                $host = 'ssl://'.$host;
250                break;
251
252            case 'tls':
253                $host = 'tls://'.$host;
254                break;
255        }
256
257        return $host;
258    }
259}
260