diff --git a/docs/book/read.md b/docs/book/read.md index 2619cd1c..4b94e9f6 100644 --- a/docs/book/read.md +++ b/docs/book/read.md @@ -117,6 +117,10 @@ $mail = new Pop3([ ]); ``` +If you are connecting to a mail server with a self-signed certificate and want to +skip the SSL verification, you can also pass an additional argument `novalidatecert` +with the value `true`. + Both constructors throw `Laminas\Mail\Exception` or `Laminas\Mail\Protocol\Exception` (extends `Laminas\Mail\Exception`) for connection errors, depending on the type of error encountered. diff --git a/src/Protocol/Imap.php b/src/Protocol/Imap.php index eb2515c4..6bbbc117 100644 --- a/src/Protocol/Imap.php +++ b/src/Protocol/Imap.php @@ -19,12 +19,6 @@ class Imap */ const TIMEOUT_CONNECTION = 30; - /** - * socket to imap server - * @var resource|null - */ - protected $socket; - /** * counter for request tag * @var int @@ -34,13 +28,16 @@ class Imap /** * Public constructor * - * @param string $host hostname or IP address of IMAP server, if given connect() is called - * @param int|null $port port of IMAP server, null for default (143 or 993 for ssl) - * @param bool $ssl use ssl? 'SSL', 'TLS' or false + * @param string $host hostname or IP address of IMAP server, if given connect() is called + * @param int|null $port port of IMAP server, null for default (143 or 993 for ssl) + * @param bool $ssl use ssl? 'SSL', 'TLS' or false + * @param bool $novalidatecert set to true to skip SSL certificate validation * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface */ - public function __construct($host = '', $port = null, $ssl = false) + public function __construct($host = '', $port = null, $ssl = false, $novalidatecert = false) { + $this->setNoValidateCert($novalidatecert); + if ($host) { $this->connect($host, $port, $ssl); } @@ -87,15 +84,7 @@ public function connect($host, $port = null, $ssl = false) } } - ErrorHandler::start(); - $this->socket = fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION); - $error = ErrorHandler::stop(); - if (! $this->socket) { - throw new Exception\RuntimeException(sprintf( - 'cannot connect to host %s', - ($error ? sprintf('; error = %s (errno = %d )', $error->getMessage(), $error->getCode()) : '') - ), 0, $error); - } + $this->setupSocket($host, $port); if (! $this->assumedNextLine('* OK')) { throw new Exception\RuntimeException('host doesn\'t allow connection'); diff --git a/src/Protocol/Pop3.php b/src/Protocol/Pop3.php index 342644bc..8d31dc7d 100644 --- a/src/Protocol/Pop3.php +++ b/src/Protocol/Pop3.php @@ -25,12 +25,6 @@ class Pop3 */ public $hasTop = null; - /** - * socket to pop3 - * @var null|resource - */ - protected $socket; - /** * greeting timestamp for apop * @var null|string @@ -40,12 +34,15 @@ class Pop3 /** * Public constructor * - * @param string $host hostname or IP address of POP3 server, if given connect() is called - * @param int|null $port port of POP3 server, null for default (110 or 995 for ssl) - * @param bool|string $ssl use ssl? 'SSL', 'TLS' or false + * @param string $host hostname or IP address of POP3 server, if given connect() is called + * @param int|null $port port of POP3 server, null for default (110 or 995 for ssl) + * @param bool|string $ssl use ssl? 'SSL', 'TLS' or false + * @param bool $novalidatecert set to true to skip SSL certificate validation */ - public function __construct($host = '', $port = null, $ssl = false) + public function __construct($host = '', $port = null, $ssl = false, $novalidatecert = false) { + $this->setNoValidateCert($novalidatecert); + if ($host) { $this->connect($host, $port, $ssl); } @@ -92,15 +89,7 @@ public function connect($host, $port = null, $ssl = false) } } - ErrorHandler::start(); - $this->socket = fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION); - $error = ErrorHandler::stop(); - if (! $this->socket) { - throw new Exception\RuntimeException(sprintf( - 'cannot connect to host %s', - ($error ? sprintf('; error = %s (errno = %d )', $error->getMessage(), $error->getCode()) : '') - ), 0, $error); - } + $this->setupSocket($host, $port); $welcome = $this->readResponse(); diff --git a/src/Protocol/ProtocolTrait.php b/src/Protocol/ProtocolTrait.php index 17655f02..0e0faccb 100644 --- a/src/Protocol/ProtocolTrait.php +++ b/src/Protocol/ProtocolTrait.php @@ -13,6 +13,12 @@ */ trait ProtocolTrait { + /** + * If set to true, do not validate the SSL certificate + * @var null|bool + */ + protected $novalidatecert; + public function getCryptoMethod() { // Allow the best TLS version(s) we can @@ -27,4 +33,72 @@ public function getCryptoMethod() return $cryptoMethod; } + + /** + * Do not validate SSL certificate + * + * @param bool $novalidatecert Set to true to disable certificate validation + * + * @return Imap|Pop3 + */ + public function setNoValidateCert(bool $novalidatecert) + { + $this->novalidatecert = $novalidatecert; + return $this; + } + + /** + * Should we validate SSL certificate? + * + * @return bool + */ + public function validateCert() + { + return ! $this->novalidatecert; + } + + /** + * Prepare socket options + * + * @return array + */ + private function prepareSocketOptions() + { + return $this->novalidatecert + ? [ + 'ssl' => [ + 'verify_peer_name' => false, + 'verify_peer' => false, + ] + ] : []; + } + + /** + * Setup connection socket + * + * @param string $host hostname or IP address of IMAP server + * @param int|null $port of IMAP server, default is 143 (993 for ssl) + * + * @return void + */ + protected function setupSocket($host, $port) + { + ErrorHandler::start(); + $this->socket = stream_socket_client( + $host . ":" . $port, + $errno, + $errstr, + self::TIMEOUT_CONNECTION, + STREAM_CLIENT_CONNECT, + stream_context_create($this->prepareSocketOptions()) + ); + $error = ErrorHandler::stop(); + + if (! $this->socket) { + throw new Exception\RuntimeException(sprintf( + 'cannot connect to host %s', + ($error ? sprintf('; error = %s (errno = %d )', $error->getMessage(), $error->getCode()) : '') + ), 0, $error); + } + } } diff --git a/src/Storage/Imap.php b/src/Storage/Imap.php index 029e950e..e4acf9e1 100644 --- a/src/Storage/Imap.php +++ b/src/Storage/Imap.php @@ -213,6 +213,11 @@ public function __construct($params) $ssl = isset($params->ssl) ? $params->ssl : false; $this->protocol = new Protocol\Imap(); + + if (isset($params->novalidatecert)) { + $this->protocol->setNoValidateCert((bool)$params->novalidatecert); + } + $this->protocol->connect($host, $port, $ssl); if (! $this->protocol->login($params->user, $password)) { throw new Exception\RuntimeException('cannot login, user or password wrong'); diff --git a/src/Storage/Pop3.php b/src/Storage/Pop3.php index 7e799ae9..3549763a 100644 --- a/src/Storage/Pop3.php +++ b/src/Storage/Pop3.php @@ -146,6 +146,11 @@ public function __construct($params) $ssl = isset($params->ssl) ? $params->ssl : false; $this->protocol = new Protocol\Pop3(); + + if (isset($params->novalidatecert)) { + $this->protocol->setNoValidateCert((bool)$params->novalidatecert); + } + $this->protocol->connect($host, $port, $ssl); $this->protocol->login($params->user, $password); } diff --git a/test/Storage/ImapTest.php b/test/Storage/ImapTest.php index 1ff15df0..84b519ef 100644 --- a/test/Storage/ImapTest.php +++ b/test/Storage/ImapTest.php @@ -130,6 +130,17 @@ public function testConnectTLS() new Storage\Imap($this->params); } + public function testConnectSelfSignedSSL() + { + if (! getenv('TESTS_LAMINAS_MAIL_IMAP_SSL')) { + return; + } + + $this->params['ssl'] = 'SSL'; + $this->params['novalidatecert'] = true; + new Storage\Imap($this->params); + } + public function testInvalidService() { $this->params['port'] = getenv('TESTS_LAMINAS_MAIL_IMAP_INVALID_PORT'); diff --git a/test/Storage/Pop3Test.php b/test/Storage/Pop3Test.php index d50ffd09..07e75075 100644 --- a/test/Storage/Pop3Test.php +++ b/test/Storage/Pop3Test.php @@ -138,6 +138,18 @@ public function testConnectTLS() new Storage\Pop3($this->params); } + public function testConnectSelfSignedSSL() + { + if (! getenv('TESTS_LAMINAS_MAIL_POP3_SSL')) { + return; + } + + $this->params['ssl'] = 'SSL'; + $this->params['novalidatecert'] = true; + + new Storage\Pop3($this->params); + } + public function testInvalidService() { $this->params['port'] = getenv('TESTS_LAMINAS_MAIL_POP3_INVALID_PORT');