1<?php
2
3/**
4 * PHPMailer POP-Before-SMTP Authentication Class.
5 * PHP Version 5.5.
6 *
7 * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
8 *
9 * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
10 * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
11 * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
12 * @author    Brent R. Matzelle (original founder)
13 * @copyright 2012 - 2020 Marcus Bointon
14 * @copyright 2010 - 2012 Jim Jagielski
15 * @copyright 2004 - 2009 Andy Prevost
16 * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
17 * @note      This program is distributed in the hope that it will be useful - WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE.
20 */
21
22namespace PHPMailer\PHPMailer;
23
24/**
25 * PHPMailer POP-Before-SMTP Authentication Class.
26 * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
27 * 1) This class does not support APOP authentication.
28 * 2) Opening and closing lots of POP3 connections can be quite slow. If you need
29 *   to send a batch of emails then just perform the authentication once at the start,
30 *   and then loop through your mail sending script. Providing this process doesn't
31 *   take longer than the verification period lasts on your POP3 server, you should be fine.
32 * 3) This is really ancient technology; you should only need to use it to talk to very old systems.
33 * 4) This POP3 class is deliberately lightweight and incomplete, implementing just
34 *   enough to do authentication.
35 *   If you want a more complete class there are other POP3 classes for PHP available.
36 *
37 * @author Richard Davey (original author) <rich@corephp.co.uk>
38 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
39 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
40 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
41 */
42class POP3
43{
44    /**
45     * The POP3 PHPMailer Version number.
46     *
47     * @var string
48     */
49    const VERSION = '6.3.0';
50
51    /**
52     * Default POP3 port number.
53     *
54     * @var int
55     */
56    const DEFAULT_PORT = 110;
57
58    /**
59     * Default timeout in seconds.
60     *
61     * @var int
62     */
63    const DEFAULT_TIMEOUT = 30;
64
65    /**
66     * POP3 class debug output mode.
67     * Debug output level.
68     * Options:
69     * @see POP3::DEBUG_OFF: No output
70     * @see POP3::DEBUG_SERVER: Server messages, connection/server errors
71     * @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors
72     *
73     * @var int
74     */
75    public $do_debug = self::DEBUG_OFF;
76
77    /**
78     * POP3 mail server hostname.
79     *
80     * @var string
81     */
82    public $host;
83
84    /**
85     * POP3 port number.
86     *
87     * @var int
88     */
89    public $port;
90
91    /**
92     * POP3 Timeout Value in seconds.
93     *
94     * @var int
95     */
96    public $tval;
97
98    /**
99     * POP3 username.
100     *
101     * @var string
102     */
103    public $username;
104
105    /**
106     * POP3 password.
107     *
108     * @var string
109     */
110    public $password;
111
112    /**
113     * Resource handle for the POP3 connection socket.
114     *
115     * @var resource
116     */
117    protected $pop_conn;
118
119    /**
120     * Are we connected?
121     *
122     * @var bool
123     */
124    protected $connected = false;
125
126    /**
127     * Error container.
128     *
129     * @var array
130     */
131    protected $errors = [];
132
133    /**
134     * Line break constant.
135     */
136    const LE = "\r\n";
137
138    /**
139     * Debug level for no output.
140     *
141     * @var int
142     */
143    const DEBUG_OFF = 0;
144
145    /**
146     * Debug level to show server -> client messages
147     * also shows clients connection errors or errors from server
148     *
149     * @var int
150     */
151    const DEBUG_SERVER = 1;
152
153    /**
154     * Debug level to show client -> server and server -> client messages.
155     *
156     * @var int
157     */
158    const DEBUG_CLIENT = 2;
159
160    /**
161     * Simple static wrapper for all-in-one POP before SMTP.
162     *
163     * @param string   $host        The hostname to connect to
164     * @param int|bool $port        The port number to connect to
165     * @param int|bool $timeout     The timeout value
166     * @param string   $username
167     * @param string   $password
168     * @param int      $debug_level
169     *
170     * @return bool
171     */
172    public static function popBeforeSmtp(
173        $host,
174        $port = false,
175        $timeout = false,
176        $username = '',
177        $password = '',
178        $debug_level = 0
179    ) {
180        $pop = new self();
181
182        return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level);
183    }
184
185    /**
186     * Authenticate with a POP3 server.
187     * A connect, login, disconnect sequence
188     * appropriate for POP-before SMTP authorisation.
189     *
190     * @param string   $host        The hostname to connect to
191     * @param int|bool $port        The port number to connect to
192     * @param int|bool $timeout     The timeout value
193     * @param string   $username
194     * @param string   $password
195     * @param int      $debug_level
196     *
197     * @return bool
198     */
199    public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
200    {
201        $this->host = $host;
202        //If no port value provided, use default
203        if (false === $port) {
204            $this->port = static::DEFAULT_PORT;
205        } else {
206            $this->port = (int) $port;
207        }
208        //If no timeout value provided, use default
209        if (false === $timeout) {
210            $this->tval = static::DEFAULT_TIMEOUT;
211        } else {
212            $this->tval = (int) $timeout;
213        }
214        $this->do_debug = $debug_level;
215        $this->username = $username;
216        $this->password = $password;
217        //Reset the error log
218        $this->errors = [];
219        //Connect
220        $result = $this->connect($this->host, $this->port, $this->tval);
221        if ($result) {
222            $login_result = $this->login($this->username, $this->password);
223            if ($login_result) {
224                $this->disconnect();
225
226                return true;
227            }
228        }
229        //We need to disconnect regardless of whether the login succeeded
230        $this->disconnect();
231
232        return false;
233    }
234
235    /**
236     * Connect to a POP3 server.
237     *
238     * @param string   $host
239     * @param int|bool $port
240     * @param int      $tval
241     *
242     * @return bool
243     */
244    public function connect($host, $port = false, $tval = 30)
245    {
246        //Are we already connected?
247        if ($this->connected) {
248            return true;
249        }
250
251        //On Windows this will raise a PHP Warning error if the hostname doesn't exist.
252        //Rather than suppress it with @fsockopen, capture it cleanly instead
253        set_error_handler([$this, 'catchWarning']);
254
255        if (false === $port) {
256            $port = static::DEFAULT_PORT;
257        }
258
259        //Connect to the POP3 server
260        $errno = 0;
261        $errstr = '';
262        $this->pop_conn = fsockopen(
263            $host, //POP3 Host
264            $port, //Port #
265            $errno, //Error Number
266            $errstr, //Error Message
267            $tval
268        ); //Timeout (seconds)
269        //Restore the error handler
270        restore_error_handler();
271
272        //Did we connect?
273        if (false === $this->pop_conn) {
274            //It would appear not...
275            $this->setError(
276                "Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr"
277            );
278
279            return false;
280        }
281
282        //Increase the stream time-out
283        stream_set_timeout($this->pop_conn, $tval, 0);
284
285        //Get the POP3 server response
286        $pop3_response = $this->getResponse();
287        //Check for the +OK
288        if ($this->checkResponse($pop3_response)) {
289            //The connection is established and the POP3 server is talking
290            $this->connected = true;
291
292            return true;
293        }
294
295        return false;
296    }
297
298    /**
299     * Log in to the POP3 server.
300     * Does not support APOP (RFC 2828, 4949).
301     *
302     * @param string $username
303     * @param string $password
304     *
305     * @return bool
306     */
307    public function login($username = '', $password = '')
308    {
309        if (!$this->connected) {
310            $this->setError('Not connected to POP3 server');
311        }
312        if (empty($username)) {
313            $username = $this->username;
314        }
315        if (empty($password)) {
316            $password = $this->password;
317        }
318
319        //Send the Username
320        $this->sendString("USER $username" . static::LE);
321        $pop3_response = $this->getResponse();
322        if ($this->checkResponse($pop3_response)) {
323            //Send the Password
324            $this->sendString("PASS $password" . static::LE);
325            $pop3_response = $this->getResponse();
326            if ($this->checkResponse($pop3_response)) {
327                return true;
328            }
329        }
330
331        return false;
332    }
333
334    /**
335     * Disconnect from the POP3 server.
336     */
337    public function disconnect()
338    {
339        $this->sendString('QUIT');
340        //The QUIT command may cause the daemon to exit, which will kill our connection
341        //So ignore errors here
342        try {
343            @fclose($this->pop_conn);
344        } catch (Exception $e) {
345            //Do nothing
346        }
347    }
348
349    /**
350     * Get a response from the POP3 server.
351     *
352     * @param int $size The maximum number of bytes to retrieve
353     *
354     * @return string
355     */
356    protected function getResponse($size = 128)
357    {
358        $response = fgets($this->pop_conn, $size);
359        if ($this->do_debug >= self::DEBUG_SERVER) {
360            echo 'Server -> Client: ', $response;
361        }
362
363        return $response;
364    }
365
366    /**
367     * Send raw data to the POP3 server.
368     *
369     * @param string $string
370     *
371     * @return int
372     */
373    protected function sendString($string)
374    {
375        if ($this->pop_conn) {
376            if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2
377                echo 'Client -> Server: ', $string;
378            }
379
380            return fwrite($this->pop_conn, $string, strlen($string));
381        }
382
383        return 0;
384    }
385
386    /**
387     * Checks the POP3 server response.
388     * Looks for for +OK or -ERR.
389     *
390     * @param string $string
391     *
392     * @return bool
393     */
394    protected function checkResponse($string)
395    {
396        if (strpos($string, '+OK') !== 0) {
397            $this->setError("Server reported an error: $string");
398
399            return false;
400        }
401
402        return true;
403    }
404
405    /**
406     * Add an error to the internal error store.
407     * Also display debug output if it's enabled.
408     *
409     * @param string $error
410     */
411    protected function setError($error)
412    {
413        $this->errors[] = $error;
414        if ($this->do_debug >= self::DEBUG_SERVER) {
415            echo '<pre>';
416            foreach ($this->errors as $e) {
417                print_r($e);
418            }
419            echo '</pre>';
420        }
421    }
422
423    /**
424     * Get an array of error messages, if any.
425     *
426     * @return array
427     */
428    public function getErrors()
429    {
430        return $this->errors;
431    }
432
433    /**
434     * POP3 connection error handler.
435     *
436     * @param int    $errno
437     * @param string $errstr
438     * @param string $errfile
439     * @param int    $errline
440     */
441    protected function catchWarning($errno, $errstr, $errfile, $errline)
442    {
443        $this->setError(
444            'Connecting to the POP3 server raised a PHP warning:' .
445            "errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
446        );
447    }
448}
449