From 27a895d7d8789334def82f32402c6bb282ed632a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Sat, 21 Jul 2012 14:23:47 +0100 Subject: [PATCH 1/5] adding enableCrypto() method to CakeSocket class --- lib/Cake/Network/CakeSocket.php | 53 ++++++++- lib/Cake/Test/Case/Network/CakeSocketTest.php | 109 ++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Network/CakeSocket.php b/lib/Cake/Network/CakeSocket.php index aa0eb4fbd..d1d597ff6 100644 --- a/lib/Cake/Network/CakeSocket.php +++ b/lib/Cake/Network/CakeSocket.php @@ -76,6 +76,27 @@ class CakeSocket { */ public $lastError = array(); +/** + * True if the socket stream is encrypted after a CakeSocket::enableCrypto() call + * @var type + */ + public $encrypted = false; + +/** + * Contains all the encryption methods available + * @var array + */ + protected $_encryptMethods = array( + 'sslv2_client' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT, + 'sslv3_client' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + 'sslv23_client' =>STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT, + 'sslv2_server' => STREAM_CRYPTO_METHOD_SSLv2_SERVER, + 'sslv3_server' => STREAM_CRYPTO_METHOD_SSLv3_SERVER, + 'sslv23_server' => STREAM_CRYPTO_METHOD_SSLv23_SERVER, + 'tls_server' => STREAM_CRYPTO_METHOD_TLS_SERVER + ); + /** * Constructor. * @@ -277,4 +298,34 @@ class CakeSocket { return true; } -} +/** + * Encrypts current stream socket, using one of the defined encryption methods + * + * @param string $type can be one of 'ssl2', 'ssl3', 'ssl23' or 'tls' + * @param string $clientOrServer can be one of 'client', 'server'. Default is 'client' + * @param boolean $enable enable or disable encryption. Default is true (enable) + * @return boolean True on success + * @throws SocketException + * @see stream_socket_enable_crypto + */ + public function enableCrypto($type, $clientOrServer = 'client', $enable = true) { + if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) { + throw new InvalidArgumentException(); + } + $enableCryptoResult = false; + try { + $enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable, $this->_encryptMethods[$type . '_' . $clientOrServer]); + } catch (Exception $e) { + $this->setLastError(null, $e->getMessage()); + throw new SocketException($e->getMessage()); + } + if ($enableCryptoResult === true) { + $this->encrypted = $enable; + return true; + } else { + $errorMessage = __('Unable to perform enableCrypto operation on CakeSocket'); + $this->setLastError(null, $errorMessage); + throw new SocketException($errorMessage); + } + } +} \ No newline at end of file diff --git a/lib/Cake/Test/Case/Network/CakeSocketTest.php b/lib/Cake/Test/Case/Network/CakeSocketTest.php index abe6fcadf..9baf63df3 100644 --- a/lib/Cake/Test/Case/Network/CakeSocketTest.php +++ b/lib/Cake/Test/Case/Network/CakeSocketTest.php @@ -214,4 +214,113 @@ class CakeSocketTest extends CakeTestCase { $anotherSocket->reset(); $this->assertEquals(array(), $anotherSocket->config); } + +/** + * testEncrypt + * + * @return void + */ + public function testEnableCryptoSocketExceptionNoSsl() { + $configNoSslOrTls = array('host' => 'localhost', 'port' => 80, 'timeout' => 0.1); + + // testing exception on no ssl socket server for ssl and tls methods + $this->Socket = new CakeSocket($configNoSslOrTls); + $this->Socket->connect(); + $this->setExpectedException('SocketException'); + $this->Socket->enableCrypto('sslv3', 'client'); + } + +/** + * testEnableCryptoSocketExceptionNoTls + * + * @return void + */ + public function testEnableCryptoSocketExceptionNoTls() { + $configNoSslOrTls = array('host' => 'localhost', 'port' => 80, 'timeout' => 0.1); + + // testing exception on no ssl socket server for ssl and tls methods + $this->Socket = new CakeSocket($configNoSslOrTls); + $this->Socket->connect(); + $this->setExpectedException('SocketException'); + $this->Socket->enableCrypto('tls', 'client'); + } + +/** + * _connectSocketToSslTls + * + * @return void + */ + protected function _connectSocketToSslTls() { + $configSslTls = array('host' => 'smtp.gmail.com', 'port' => 465, 'timeout' => 5); + $this->Socket = new CakeSocket($configSslTls); + $this->Socket->connect(); + } + +/** + * testEnableCryptoBadMode + * + * @return void + */ + public function testEnableCryptoBadMode() { + // testing wrong encryption mode + $this->_connectSocketToSslTls(); + $this->setExpectedException('InvalidArgumentException'); + $this->Socket->enableCrypto('doesntExistMode', 'server'); + $this->Socket->disconnect(); + } + +/** + * testEnableCrypto + * + * @return void + */ + public function testEnableCrypto() { + // testing on ssl server + $this->_connectSocketToSslTls(); + $this->assertTrue($this->Socket->enableCrypto('sslv3', 'client')); + $this->Socket->disconnect(); + + // testing on tls server + $this->_connectSocketToSslTls(); + $this->assertTrue($this->Socket->enableCrypto('tls', 'client')); + $this->Socket->disconnect(); + } + +/** + * testEnableCryptoExceptionEnableTwice + * + * @return void + */ + public function testEnableCryptoExceptionEnableTwice() { + // testing on tls server + $this->_connectSocketToSslTls(); + $this->Socket->enableCrypto('tls', 'client'); + $this->setExpectedException('SocketException'); + $this->Socket->enableCrypto('tls', 'client'); + } + +/** + * testEnableCryptoExceptionDisableTwice + * + * @return void + */ + public function testEnableCryptoExceptionDisableTwice() { + // testing on tls server + $this->_connectSocketToSslTls(); + $this->setExpectedException('SocketException'); + $this->Socket->enableCrypto('tls', 'client', false); + } + +/** + * testEnableCryptoEnableStatus + * + * @return void + */ + public function testEnableCryptoEnableStatus() { + // testing on tls server + $this->_connectSocketToSslTls(); + $this->assertFalse($this->Socket->encrypted); + $this->Socket->enableCrypto('tls', 'client', true); + $this->assertTrue($this->Socket->encrypted); + } } From 55c92065c674a9241b2ee36c32865a85b75d4443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Sat, 21 Jul 2012 20:28:38 +0100 Subject: [PATCH 2/5] adding a tls option to SmtpTransport to support TLS SMTP servers like MS Exchange 2010 --- lib/Cake/Network/Email/SmtpTransport.php | 13 +++- .../Case/Network/Email/SmtpTransportTest.php | 66 ++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Network/Email/SmtpTransport.php b/lib/Cake/Network/Email/SmtpTransport.php index 363e477e8..eb3c13595 100644 --- a/lib/Cake/Network/Email/SmtpTransport.php +++ b/lib/Cake/Network/Email/SmtpTransport.php @@ -79,7 +79,8 @@ class SmtpTransport extends AbstractTransport { 'timeout' => 30, 'username' => null, 'password' => null, - 'client' => null + 'client' => null, + 'tls' => false ); $this->_config = $config + $default; } @@ -107,7 +108,15 @@ class SmtpTransport extends AbstractTransport { try { $this->_smtpSend("EHLO {$host}", '250'); + if ($this->_config['tls']) { + $this->_smtpSend("STARTTLS", '220'); + $this->_socket->enableCrypto('tls'); + $this->_smtpSend("EHLO {$host}", '250'); + } } catch (SocketException $e) { + if ($this->_config['tls']) { + throw new SocketException(__d('cake_dev', 'SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.')); + } try { $this->_smtpSend("HELO {$host}", '250'); } catch (SocketException $e2) { @@ -132,6 +141,8 @@ class SmtpTransport extends AbstractTransport { if (!$this->_smtpSend(base64_encode($this->_config['password']), '235')) { throw new SocketException(__d('cake_dev', 'SMTP server did not accept the password.')); } + } elseif ($authRequired == '504') { + throw new SocketException(__d('cake_dev', 'SMTP authentication method not allowed, check if SMTP server requires TLS')); } elseif ($authRequired != '503') { throw new SocketException(__d('cake_dev', 'SMTP does not require authentication.')); } diff --git a/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php b/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php index 0310e5320..f5fa2f95d 100644 --- a/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php +++ b/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php @@ -81,7 +81,7 @@ class SmtpTransportTest extends CakeTestCase { */ public function setUp() { if (!class_exists('MockSocket')) { - $this->getMock('CakeSocket', array('read', 'write', 'connect'), array(), 'MockSocket'); + $this->getMock('CakeSocket', array('read', 'write', 'connect', 'enableCrypto'), array(), 'MockSocket'); } $this->socket = new MockSocket(); @@ -105,6 +105,70 @@ class SmtpTransportTest extends CakeTestCase { $this->SmtpTransport->connect(); } +/** + * testConnectEhloTls method + * + * @return void + */ + public function testConnectEhloTls() { + $this->SmtpTransport->config(array('tls' => true)); + $this->socket->expects($this->any())->method('connect')->will($this->returnValue(true)); + $this->socket->expects($this->at(0))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(1))->method('read')->will($this->returnValue("220 Welcome message\r\n")); + $this->socket->expects($this->at(2))->method('write')->with("EHLO localhost\r\n"); + $this->socket->expects($this->at(3))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(4))->method('read')->will($this->returnValue("250 Accepted\r\n")); + $this->socket->expects($this->at(5))->method('write')->with("STARTTLS\r\n"); + $this->socket->expects($this->at(6))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(7))->method('read')->will($this->returnValue("220 Server ready\r\n")); + $this->socket->expects($this->at(8))->method('other')->with('tls')->will($this->returnValue(true)); + $this->socket->expects($this->at(9))->method('write')->with("EHLO localhost\r\n"); + $this->socket->expects($this->at(10))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(11))->method('read')->will($this->returnValue("250 Accepted\r\n")); + $this->SmtpTransport->connect(); + } + +/** + * testConnectEhloTlsOnNonTlsServer method + * + * @return void + */ + public function testConnectEhloTlsOnNonTlsServer() { + $this->SmtpTransport->config(array('tls' => true)); + $this->socket->expects($this->any())->method('connect')->will($this->returnValue(true)); + $this->socket->expects($this->at(0))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(1))->method('read')->will($this->returnValue("220 Welcome message\r\n")); + $this->socket->expects($this->at(2))->method('write')->with("EHLO localhost\r\n"); + $this->socket->expects($this->at(3))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(4))->method('read')->will($this->returnValue("250 Accepted\r\n")); + $this->socket->expects($this->at(5))->method('write')->with("STARTTLS\r\n"); + $this->socket->expects($this->at(6))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(7))->method('read')->will($this->returnValue("500 5.3.3 Unrecognized command\r\n")); + $this->setExpectedException('SocketException'); + $this->SmtpTransport->connect(); + } + +/** + * testConnectEhloNoTlsOnRequiredTlsServer method + * + * @return void + */ + public function testConnectEhloNoTlsOnRequiredTlsServer() { + $this->SmtpTransport->config(array('tls' => false, 'username' => 'user', 'password' => 'pass')); + $this->socket->expects($this->any())->method('connect')->will($this->returnValue(true)); + $this->socket->expects($this->at(0))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(1))->method('read')->will($this->returnValue("220 Welcome message\r\n")); + $this->socket->expects($this->at(2))->method('write')->with("EHLO localhost\r\n"); + $this->socket->expects($this->at(3))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(4))->method('read')->will($this->returnValue("250 Accepted\r\n")); + $this->socket->expects($this->at(5))->method('read')->with("AUTH LOGIN\r\n"); + $this->socket->expects($this->at(6))->method('read')->will($this->returnValue(false)); + $this->socket->expects($this->at(7))->method('read')->will($this->returnValue("504 5.7.4 Unrecognized authentication type\r\n")); + $this->setExpectedException('SocketException'); + $this->SmtpTransport->connect(); + $this->SmtpTransport->auth(); + } + /** * testConnectHelo method * From d7f93f5bcf0b8aeb0ad286fd2302ec2d123b0272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Tue, 24 Jul 2012 14:08:54 +0100 Subject: [PATCH 3/5] adding a related message to the enableCrypto InvalidArgumentException --- lib/Cake/Network/CakeSocket.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Network/CakeSocket.php b/lib/Cake/Network/CakeSocket.php index d1d597ff6..9ced3c7ce 100644 --- a/lib/Cake/Network/CakeSocket.php +++ b/lib/Cake/Network/CakeSocket.php @@ -89,7 +89,7 @@ class CakeSocket { protected $_encryptMethods = array( 'sslv2_client' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT, 'sslv3_client' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, - 'sslv23_client' =>STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + 'sslv23_client' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT, 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT, 'sslv2_server' => STREAM_CRYPTO_METHOD_SSLv2_SERVER, 'sslv3_server' => STREAM_CRYPTO_METHOD_SSLv3_SERVER, @@ -310,7 +310,7 @@ class CakeSocket { */ public function enableCrypto($type, $clientOrServer = 'client', $enable = true) { if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) { - throw new InvalidArgumentException(); + throw new InvalidArgumentException(__('Invalid encryption scheme chosen')); } $enableCryptoResult = false; try { From f9f380d4013bf50eddeb501de026e38d1a3db1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Tue, 24 Jul 2012 14:09:48 +0100 Subject: [PATCH 4/5] using annotations for catching exceptions in tests --- lib/Cake/Test/Case/Network/CakeSocketTest.php | 10 +++++----- lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/Cake/Test/Case/Network/CakeSocketTest.php b/lib/Cake/Test/Case/Network/CakeSocketTest.php index 9baf63df3..b214ca9b5 100644 --- a/lib/Cake/Test/Case/Network/CakeSocketTest.php +++ b/lib/Cake/Test/Case/Network/CakeSocketTest.php @@ -218,6 +218,7 @@ class CakeSocketTest extends CakeTestCase { /** * testEncrypt * + * @expectedException SocketException * @return void */ public function testEnableCryptoSocketExceptionNoSsl() { @@ -226,13 +227,13 @@ class CakeSocketTest extends CakeTestCase { // testing exception on no ssl socket server for ssl and tls methods $this->Socket = new CakeSocket($configNoSslOrTls); $this->Socket->connect(); - $this->setExpectedException('SocketException'); $this->Socket->enableCrypto('sslv3', 'client'); } /** * testEnableCryptoSocketExceptionNoTls * + * @expectedException SocketException * @return void */ public function testEnableCryptoSocketExceptionNoTls() { @@ -241,7 +242,6 @@ class CakeSocketTest extends CakeTestCase { // testing exception on no ssl socket server for ssl and tls methods $this->Socket = new CakeSocket($configNoSslOrTls); $this->Socket->connect(); - $this->setExpectedException('SocketException'); $this->Socket->enableCrypto('tls', 'client'); } @@ -259,12 +259,12 @@ class CakeSocketTest extends CakeTestCase { /** * testEnableCryptoBadMode * + * @expectedException InvalidArgumentException * @return void */ public function testEnableCryptoBadMode() { // testing wrong encryption mode $this->_connectSocketToSslTls(); - $this->setExpectedException('InvalidArgumentException'); $this->Socket->enableCrypto('doesntExistMode', 'server'); $this->Socket->disconnect(); } @@ -289,25 +289,25 @@ class CakeSocketTest extends CakeTestCase { /** * testEnableCryptoExceptionEnableTwice * + * @expectedException SocketException * @return void */ public function testEnableCryptoExceptionEnableTwice() { // testing on tls server $this->_connectSocketToSslTls(); $this->Socket->enableCrypto('tls', 'client'); - $this->setExpectedException('SocketException'); $this->Socket->enableCrypto('tls', 'client'); } /** * testEnableCryptoExceptionDisableTwice * + * @expectedException SocketException * @return void */ public function testEnableCryptoExceptionDisableTwice() { // testing on tls server $this->_connectSocketToSslTls(); - $this->setExpectedException('SocketException'); $this->Socket->enableCrypto('tls', 'client', false); } diff --git a/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php b/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php index f5fa2f95d..55dc7bb0a 100644 --- a/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php +++ b/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php @@ -131,6 +131,7 @@ class SmtpTransportTest extends CakeTestCase { /** * testConnectEhloTlsOnNonTlsServer method * + * @expectedException SocketException * @return void */ public function testConnectEhloTlsOnNonTlsServer() { @@ -144,13 +145,13 @@ class SmtpTransportTest extends CakeTestCase { $this->socket->expects($this->at(5))->method('write')->with("STARTTLS\r\n"); $this->socket->expects($this->at(6))->method('read')->will($this->returnValue(false)); $this->socket->expects($this->at(7))->method('read')->will($this->returnValue("500 5.3.3 Unrecognized command\r\n")); - $this->setExpectedException('SocketException'); $this->SmtpTransport->connect(); } /** * testConnectEhloNoTlsOnRequiredTlsServer method * + * @expectedException SocketException * @return void */ public function testConnectEhloNoTlsOnRequiredTlsServer() { @@ -164,7 +165,6 @@ class SmtpTransportTest extends CakeTestCase { $this->socket->expects($this->at(5))->method('read')->with("AUTH LOGIN\r\n"); $this->socket->expects($this->at(6))->method('read')->will($this->returnValue(false)); $this->socket->expects($this->at(7))->method('read')->will($this->returnValue("504 5.7.4 Unrecognized authentication type\r\n")); - $this->setExpectedException('SocketException'); $this->SmtpTransport->connect(); $this->SmtpTransport->auth(); } From 7418be04d3fd24bd7cef09527bd40e55783c28c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Wed, 25 Jul 2012 19:20:30 +0100 Subject: [PATCH 5/5] fixing translation domain to cake_dev --- lib/Cake/Network/CakeSocket.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Network/CakeSocket.php b/lib/Cake/Network/CakeSocket.php index 9ced3c7ce..d2d72e76a 100644 --- a/lib/Cake/Network/CakeSocket.php +++ b/lib/Cake/Network/CakeSocket.php @@ -310,7 +310,7 @@ class CakeSocket { */ public function enableCrypto($type, $clientOrServer = 'client', $enable = true) { if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) { - throw new InvalidArgumentException(__('Invalid encryption scheme chosen')); + throw new InvalidArgumentException(__d('cake_dev', 'Invalid encryption scheme chosen')); } $enableCryptoResult = false; try { @@ -323,7 +323,7 @@ class CakeSocket { $this->encrypted = $enable; return true; } else { - $errorMessage = __('Unable to perform enableCrypto operation on CakeSocket'); + $errorMessage = __d('cake_dev', 'Unable to perform enableCrypto operation on CakeSocket'); $this->setLastError(null, $errorMessage); throw new SocketException($errorMessage); }