2019-09-11 11:21:54 +00:00
# include <Common/config.h>
2019-10-05 19:25:31 +00:00
# if USE_SSL
2019-07-28 00:55:46 +00:00
# include "MySQLHandler.h"
# include <limits>
# include <ext/scope_guard.h>
2019-03-16 02:08:21 +00:00
# include <Columns/ColumnVector.h>
# include <Common/config_version.h>
# include <Common/NetException.h>
2019-05-26 06:52:29 +00:00
# include <Common/OpenSSLHelpers.h>
2019-07-28 00:55:46 +00:00
# include <Core/MySQLProtocol.h>
# include <Core/NamesAndTypes.h>
# include <DataStreams/copyData.h>
# include <Interpreters/executeQuery.h>
# include <IO/ReadBufferFromPocoSocket.h>
# include <IO/ReadBufferFromString.h>
# include <IO/WriteBufferFromPocoSocket.h>
2019-04-22 10:57:50 +00:00
# include <Poco/Crypto/CipherFactory.h>
2019-07-28 00:55:46 +00:00
# include <Poco/Crypto/RSAKey.h>
2019-04-29 06:05:30 +00:00
# include <Poco/Net/SecureStreamSocket.h>
# include <Poco/Net/SSLManager.h>
2019-07-28 00:55:46 +00:00
# include <Storages/IStorage.h>
2019-07-09 14:59:52 +00:00
2019-03-16 02:08:21 +00:00
namespace DB
{
2019-07-09 14:59:52 +00:00
2019-03-16 02:08:21 +00:00
using namespace MySQLProtocol ;
2019-07-09 14:59:52 +00:00
2019-04-29 06:05:30 +00:00
using Poco : : Net : : SecureStreamSocket ;
using Poco : : Net : : SSLManager ;
2019-03-16 02:08:21 +00:00
2019-07-09 14:59:52 +00:00
2019-03-26 18:30:41 +00:00
namespace ErrorCodes
2019-03-16 02:08:21 +00:00
{
2019-05-26 06:52:29 +00:00
extern const int MYSQL_CLIENT_INSUFFICIENT_CAPABILITIES ;
extern const int OPENSSL_ERROR ;
2019-03-16 02:08:21 +00:00
}
2019-08-03 11:02:40 +00:00
MySQLHandler : : MySQLHandler ( IServer & server_ , const Poco : : Net : : StreamSocket & socket_ , RSA & public_key_ , RSA & private_key_ , bool ssl_enabled , size_t connection_id_ )
2019-05-26 06:52:29 +00:00
: Poco : : Net : : TCPServerConnection ( socket_ )
, server ( server_ )
, log ( & Poco : : Logger : : get ( " MySQLHandler " ) )
, connection_context ( server . context ( ) )
2019-08-03 11:02:40 +00:00
, connection_id ( connection_id_ )
, public_key ( public_key_ )
, private_key ( private_key_ )
2019-07-28 13:12:26 +00:00
, auth_plugin ( new Authentication : : Native41 ( ) )
2019-05-26 06:52:29 +00:00
{
server_capability_flags = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | CLIENT_CONNECT_WITH_DB | CLIENT_DEPRECATE_EOF ;
if ( ssl_enabled )
server_capability_flags | = CLIENT_SSL ;
}
2019-03-16 02:08:21 +00:00
2019-03-26 18:30:41 +00:00
void MySQLHandler : : run ( )
{
2019-07-08 00:51:43 +00:00
connection_context . makeSessionContext ( ) ;
2019-05-26 06:52:29 +00:00
connection_context . setDefaultFormat ( " MySQLWire " ) ;
2019-03-16 02:08:21 +00:00
2019-05-16 03:34:04 +00:00
in = std : : make_shared < ReadBufferFromPocoSocket > ( socket ( ) ) ;
out = std : : make_shared < WriteBufferFromPocoSocket > ( socket ( ) ) ;
2019-07-16 06:39:18 +00:00
packet_sender = std : : make_shared < PacketSender > ( * in , * out , connection_context . mysql . sequence_id ) ;
2019-03-16 02:08:21 +00:00
try
{
2019-07-28 13:12:26 +00:00
Handshake handshake ( server_capability_flags , connection_id , VERSION_STRING + String ( " - " ) + VERSION_NAME , auth_plugin - > getName ( ) , auth_plugin - > getAuthPluginData ( ) ) ;
2019-05-16 03:34:04 +00:00
packet_sender - > sendPacket < Handshake > ( handshake , true ) ;
2019-03-26 18:30:41 +00:00
LOG_TRACE ( log , " Sent handshake " ) ;
2019-07-01 05:58:31 +00:00
HandshakeResponse handshake_response ;
finishHandshake ( handshake_response ) ;
2019-07-16 06:39:18 +00:00
connection_context . mysql . client_capabilities = handshake_response . capability_flags ;
2019-05-16 17:15:43 +00:00
if ( handshake_response . max_packet_size )
2019-07-16 06:39:18 +00:00
connection_context . mysql . max_packet_size = handshake_response . max_packet_size ;
if ( ! connection_context . mysql . max_packet_size )
connection_context . mysql . max_packet_size = MAX_PACKET_LENGTH ;
2019-03-26 18:30:41 +00:00
2019-08-07 19:14:58 +00:00
/* LOG_TRACE(log, "Capabilities: " << handshake_response.capability_flags
2019-03-26 18:30:41 +00:00
< < " \n max_packet_size: "
< < handshake_response . max_packet_size
< < " \n character_set: "
< < handshake_response . character_set
< < " \n user: "
< < handshake_response . username
< < " \n auth_response length: "
< < handshake_response . auth_response . length ( )
< < " \n auth_response: "
< < handshake_response . auth_response
< < " \n database: "
< < handshake_response . database
< < " \n auth_plugin_name: "
2019-08-07 19:14:58 +00:00
< < handshake_response . auth_plugin_name ) ; */
2019-03-16 02:08:21 +00:00
2019-05-26 06:52:29 +00:00
client_capability_flags = handshake_response . capability_flags ;
if ( ! ( client_capability_flags & CLIENT_PROTOCOL_41 ) )
2019-03-26 18:30:41 +00:00
throw Exception ( " Required capability: CLIENT_PROTOCOL_41. " , ErrorCodes : : MYSQL_CLIENT_INSUFFICIENT_CAPABILITIES ) ;
2019-03-16 02:08:21 +00:00
2019-07-28 13:12:26 +00:00
authenticate ( handshake_response . username , handshake_response . auth_plugin_name , handshake_response . auth_response ) ;
try
{
if ( ! handshake_response . database . empty ( ) )
connection_context . setCurrentDatabase ( handshake_response . database ) ;
connection_context . setCurrentQueryId ( " " ) ;
}
catch ( const Exception & exc )
{
log - > log ( exc ) ;
packet_sender - > sendPacket ( ERR_Packet ( exc . code ( ) , " 00000 " , exc . message ( ) ) , true ) ;
}
2019-05-16 05:36:08 +00:00
OK_Packet ok_packet ( 0 , handshake_response . capability_flags , 0 , 0 , 0 ) ;
2019-05-16 03:34:04 +00:00
packet_sender - > sendPacket ( ok_packet , true ) ;
2019-03-16 02:08:21 +00:00
2019-05-26 19:30:23 +00:00
while ( true )
2019-03-26 18:30:41 +00:00
{
2019-05-16 03:34:04 +00:00
packet_sender - > resetSequenceId ( ) ;
2019-07-14 22:13:56 +00:00
PacketPayloadReadBuffer payload = packet_sender - > getPayload ( ) ;
2019-07-19 18:29:39 +00:00
char command = 0 ;
2019-07-19 18:46:57 +00:00
payload . readStrict ( command ) ;
2019-07-14 22:13:56 +00:00
2019-07-19 17:50:42 +00:00
// For commands which are executed without MemoryTracker.
LimitReadBuffer limited_payload ( payload , 10000 , true , " too long MySQL packet. " ) ;
2019-07-19 18:29:39 +00:00
LOG_DEBUG ( log , " Received command: " < < static_cast < int > ( static_cast < unsigned char > ( command ) ) < < " . Connection id: " < < connection_id < < " . " ) ;
2019-03-26 18:30:41 +00:00
try
{
switch ( command )
{
2019-03-16 02:08:21 +00:00
case COM_QUIT :
return ;
case COM_INIT_DB :
2019-07-19 17:50:42 +00:00
comInitDB ( limited_payload ) ;
2019-03-16 02:08:21 +00:00
break ;
case COM_QUERY :
2019-07-14 22:13:56 +00:00
comQuery ( payload ) ;
2019-03-16 02:08:21 +00:00
break ;
case COM_FIELD_LIST :
2019-07-19 17:50:42 +00:00
comFieldList ( limited_payload ) ;
2019-03-16 02:08:21 +00:00
break ;
case COM_PING :
comPing ( ) ;
break ;
default :
throw Exception ( Poco : : format ( " Command %d is not implemented. " , command ) , ErrorCodes : : NOT_IMPLEMENTED ) ;
}
}
2019-03-26 18:30:41 +00:00
catch ( const NetException & exc )
{
2019-03-16 02:08:21 +00:00
log - > log ( exc ) ;
throw ;
}
2019-03-26 18:30:41 +00:00
catch ( const Exception & exc )
{
2019-03-16 02:08:21 +00:00
log - > log ( exc ) ;
2019-05-16 03:34:04 +00:00
packet_sender - > sendPacket ( ERR_Packet ( exc . code ( ) , " 00000 " , exc . message ( ) ) , true ) ;
2019-03-16 02:08:21 +00:00
}
}
}
2019-07-19 17:50:42 +00:00
catch ( const Poco : : Exception & exc )
2019-03-16 02:08:21 +00:00
{
log - > log ( exc ) ;
}
}
2019-05-16 03:34:04 +00:00
/** Reads 3 bytes, finds out whether it is SSLRequest or HandshakeResponse packet, starts secure connection, if it is SSLRequest.
2019-05-16 17:15:43 +00:00
* Reading is performed from socket instead of ReadBuffer to prevent reading part of SSL handshake .
2019-05-16 03:34:04 +00:00
* If we read it from socket , it will be impossible to start SSL connection using Poco . Size of SSLRequest packet payload is 32 bytes , thus we can read at most 36 bytes .
*/
2019-07-01 05:58:31 +00:00
void MySQLHandler : : finishHandshake ( MySQLProtocol : : HandshakeResponse & packet )
2019-04-29 06:05:30 +00:00
{
2019-05-16 17:15:43 +00:00
size_t packet_size = PACKET_HEADER_SIZE + SSL_REQUEST_PAYLOAD_SIZE ;
/// Buffer for SSLRequest or part of HandshakeResponse.
char buf [ packet_size ] ;
2019-04-29 06:05:30 +00:00
size_t pos = 0 ;
2019-05-16 17:15:43 +00:00
/// Reads at least count and at most packet_size bytes.
auto read_bytes = [ this , & buf , & pos , & packet_size ] ( size_t count ) - > void {
while ( pos < count )
2019-04-29 06:37:39 +00:00
{
2019-05-16 17:15:43 +00:00
int ret = socket ( ) . receiveBytes ( buf + pos , packet_size - pos ) ;
if ( ret = = 0 )
{
throw Exception ( " Cannot read all data. Bytes read: " + std : : to_string ( pos ) + " . Bytes expected: 3. " , ErrorCodes : : CANNOT_READ_ALL_DATA ) ;
}
pos + = ret ;
2019-04-29 06:05:30 +00:00
}
2019-05-16 17:15:43 +00:00
} ;
read_bytes ( 3 ) ; /// We can find out whether it is SSLRequest of HandshakeResponse by first 3 bytes.
2019-04-29 06:05:30 +00:00
2019-05-26 06:52:29 +00:00
size_t payload_size = unalignedLoad < uint32_t > ( buf ) & 0xFFFFFFu ;
2019-05-16 17:15:43 +00:00
LOG_TRACE ( log , " payload size: " < < payload_size ) ;
2019-05-16 03:34:04 +00:00
2019-05-16 17:15:43 +00:00
if ( payload_size = = SSL_REQUEST_PAYLOAD_SIZE )
2019-04-29 06:37:39 +00:00
{
2019-05-16 17:15:43 +00:00
read_bytes ( packet_size ) ; /// Reading rest SSLRequest.
SSLRequest ssl_request ;
2019-07-14 22:13:56 +00:00
ReadBufferFromMemory payload ( buf , pos ) ;
payload . ignore ( PACKET_HEADER_SIZE ) ;
ssl_request . readPayload ( payload ) ;
2019-07-16 06:39:18 +00:00
connection_context . mysql . client_capabilities = ssl_request . capability_flags ;
connection_context . mysql . max_packet_size = ssl_request . max_packet_size ? ssl_request . max_packet_size : MAX_PACKET_LENGTH ;
2019-04-29 06:05:30 +00:00
secure_connection = true ;
2019-05-16 03:34:04 +00:00
ss = std : : make_shared < SecureStreamSocket > ( SecureStreamSocket : : attach ( socket ( ) , SSLManager : : instance ( ) . defaultServerContext ( ) ) ) ;
in = std : : make_shared < ReadBufferFromPocoSocket > ( * ss ) ;
out = std : : make_shared < WriteBufferFromPocoSocket > ( * ss ) ;
2019-07-16 06:39:18 +00:00
connection_context . mysql . sequence_id = 2 ;
packet_sender = std : : make_shared < PacketSender > ( * in , * out , connection_context . mysql . sequence_id ) ;
packet_sender - > max_packet_size = connection_context . mysql . max_packet_size ;
2019-05-16 03:34:04 +00:00
packet_sender - > receivePacket ( packet ) ; /// Reading HandshakeResponse from secure socket.
2019-04-29 06:37:39 +00:00
}
else
{
2019-05-16 03:34:04 +00:00
/// Reading rest of HandshakeResponse.
2019-05-16 17:15:43 +00:00
packet_size = PACKET_HEADER_SIZE + payload_size ;
2019-07-14 22:13:56 +00:00
WriteBufferFromOwnString buf_for_handshake_response ;
2019-05-16 17:15:43 +00:00
buf_for_handshake_response . write ( buf , pos ) ;
copyData ( * packet_sender - > in , buf_for_handshake_response , packet_size - pos ) ;
2019-07-14 22:13:56 +00:00
ReadBufferFromString payload ( buf_for_handshake_response . str ( ) ) ;
payload . ignore ( PACKET_HEADER_SIZE ) ;
packet . readPayload ( payload ) ;
2019-05-16 03:34:04 +00:00
packet_sender - > sequence_id + + ;
2019-04-29 06:05:30 +00:00
}
}
2019-04-22 10:57:50 +00:00
2019-07-28 13:12:26 +00:00
void MySQLHandler : : authenticate ( const String & user_name , const String & auth_plugin_name , const String & initial_auth_response )
2019-04-22 10:57:50 +00:00
{
2019-07-28 14:17:33 +00:00
// For compatibility with JavaScript MySQL client, Native41 authentication plugin is used when possible (if password is specified using double SHA1). Otherwise SHA256 plugin is used.
2019-07-28 13:12:26 +00:00
auto user = connection_context . getUser ( user_name ) ;
2019-07-28 14:17:33 +00:00
if ( user - > password_double_sha1_hex . empty ( ) )
2019-07-28 13:12:26 +00:00
auth_plugin = std : : make_unique < Authentication : : Sha256Password > ( public_key , private_key , log ) ;
try {
std : : optional < String > auth_response = auth_plugin_name = = auth_plugin - > getName ( ) ? std : : make_optional < String > ( initial_auth_response ) : std : : nullopt ;
auth_plugin - > authenticate ( user_name , auth_response , connection_context , packet_sender , secure_connection , socket ( ) . address ( ) ) ;
2019-04-22 10:57:50 +00:00
}
catch ( const Exception & exc )
{
2019-07-28 13:12:26 +00:00
LOG_ERROR ( log , " Authentication for user " < < user_name < < " failed. " ) ;
2019-05-16 03:34:04 +00:00
packet_sender - > sendPacket ( ERR_Packet ( exc . code ( ) , " 00000 " , exc . message ( ) ) , true ) ;
2019-04-22 10:57:50 +00:00
throw ;
}
2019-07-28 13:12:26 +00:00
LOG_INFO ( log , " Authentication for user " < < user_name < < " succeeded. " ) ;
2019-04-22 10:57:50 +00:00
}
2019-07-14 22:13:56 +00:00
void MySQLHandler : : comInitDB ( ReadBuffer & payload )
2019-03-16 02:08:21 +00:00
{
2019-07-14 22:13:56 +00:00
String database ;
readStringUntilEOF ( database , payload ) ;
2019-03-16 02:08:21 +00:00
LOG_DEBUG ( log , " Setting current database to " < < database ) ;
connection_context . setCurrentDatabase ( database ) ;
2019-05-26 06:52:29 +00:00
packet_sender - > sendPacket ( OK_Packet ( 0 , client_capability_flags , 0 , 0 , 1 ) , true ) ;
2019-03-16 02:08:21 +00:00
}
2019-07-14 22:13:56 +00:00
void MySQLHandler : : comFieldList ( ReadBuffer & payload )
2019-03-26 18:30:41 +00:00
{
2019-03-16 02:08:21 +00:00
ComFieldList packet ;
2019-07-14 22:13:56 +00:00
packet . readPayload ( payload ) ;
2019-03-26 18:30:41 +00:00
String database = connection_context . getCurrentDatabase ( ) ;
StoragePtr tablePtr = connection_context . getTable ( database , packet . table ) ;
2019-03-16 02:08:21 +00:00
for ( const NameAndTypePair & column : tablePtr - > getColumns ( ) . getAll ( ) )
{
ColumnDefinition column_definition (
2019-03-26 18:30:41 +00:00
database , packet . table , packet . table , column . name , column . name , CharacterSet : : binary , 100 , ColumnType : : MYSQL_TYPE_STRING , 0 , 0
2019-03-16 02:08:21 +00:00
) ;
2019-05-16 03:34:04 +00:00
packet_sender - > sendPacket ( column_definition ) ;
2019-03-16 02:08:21 +00:00
}
2019-05-26 06:52:29 +00:00
packet_sender - > sendPacket ( OK_Packet ( 0xfe , client_capability_flags , 0 , 0 , 0 ) , true ) ;
2019-03-16 02:08:21 +00:00
}
2019-03-26 18:30:41 +00:00
void MySQLHandler : : comPing ( )
{
2019-05-26 06:52:29 +00:00
packet_sender - > sendPacket ( OK_Packet ( 0x0 , client_capability_flags , 0 , 0 , 0 ) , true ) ;
2019-03-16 02:08:21 +00:00
}
2019-07-14 22:13:56 +00:00
void MySQLHandler : : comQuery ( ReadBuffer & payload )
2019-03-26 18:30:41 +00:00
{
2019-05-16 03:34:04 +00:00
bool with_output = false ;
std : : function < void ( const String & ) > set_content_type = [ & with_output ] ( const String & ) - > void {
with_output = true ;
} ;
2019-05-25 16:15:38 +00:00
2019-07-18 04:54:26 +00:00
const String query ( " select '' " ) ;
ReadBufferFromString empty_select ( query ) ;
2019-07-16 07:11:59 +00:00
bool should_replace = false ;
// Translate query from MySQL to ClickHouse.
// This is a temporary workaround until ClickHouse supports the syntax "@@var_name".
if ( std : : string ( payload . position ( ) , payload . buffer ( ) . end ( ) ) = = " select @@version_comment limit 1 " ) // MariaDB client starts session with that query
{
should_replace = true ;
}
2019-09-09 01:04:37 +00:00
Context query_context = connection_context ;
executeQuery ( should_replace ? empty_select : payload , * out , true , query_context , set_content_type , nullptr ) ;
2019-07-14 08:22:55 +00:00
2019-05-16 03:45:17 +00:00
if ( ! with_output )
2019-05-26 06:52:29 +00:00
packet_sender - > sendPacket ( OK_Packet ( 0x00 , client_capability_flags , 0 , 0 , 0 ) , true ) ;
2019-03-16 02:08:21 +00:00
}
}
2019-09-11 11:21:54 +00:00
# endif