
973b983 Merge branch 'release-2.0.0' into develop 255dd23 Make spinlock functions take const argument 6e23bab Fix bitmask reallocation 338c189 Rename and clean up slavelag filter 3ea8f28 Fix possible NULL pointer dereference bfe6738 MXS-830: Add module information to logged messages 1fad962 Fix strncat usage d38997a Adjust log throttling policy 0be4e4b Add hashtable_item_strcasecmp 726100e Take hashtable convenience functions into use 5e7744a Fix typo in maxadmin.md c5778c8 Merge branch 'release-2.0.0' into develop b5762af Move from tmpnam to mkstemp d6f2c71 Add convenience functions to hashtable 359058a MXS-825: Add support for --execdir 636347c Enable runtime reconfiguration of log throttling ef9fba9 Improve log throttling documentation aef917a Implement log throttling e3a5349 Remove shardrouter.c 8051e80 Remove custom qc_sqlite allocation functions fd34d60 Initial implementation of the learning firewall a8752a8 Removed "filestem option" from example 1ef2519 Removed "filestem option" from example 0815cc8 Cleanup spinlock.h ab4dc99 Clean up hashtable.h ef2c078 Add prototypes for hashtable copy and free functions fb5cfaf Add 'log_throttling' configuration entry 300d823 Add proper prototypes for hashtable hash and cmp functions 1c649aa qc_mysqlembedded: Include skygw_...h without path. d276160 Add missing RPM scripts e70e644 Fix HTTPAuth installation 1b2b389 Combine utils into the server directory 3ff9913 Add missing utils headers to devel package 407efb2 Fix minor packaging problems 99aa6ad Split MaxScale into core, experimental and devel packages 1290386 Merge branch 'develop' of ssh://github.com/mariadb-corporation/maxscale-new into develop e59f148 Make scripts POSIX sh compatible 7319266 Fixed SHOW SLAVE STATUS in bonlog router f8d760a Update Binlogrouter.md 0a904ed Update Replication-Proxy-Binlog-Router-Tutorial.md 75d4202 Update Replication-Proxy-Binlog-Router-Tutorial.md b8651fc Add missing newline in listmanager.h c7ad047 Add note about user data caches to release notes 70ccc2b Merge branch 'release-2.0.0' into develop 575d1b6 Mistake - dummy session needs list markers set. 8364508 Merge branch 'develop' into binlog_server_semisync 868b902 Update MaxScale limitations 2c8b327 Store listener caches in separate directories 6e183ec Create unique user data caches for each listeners f643685 Don't free orphaned tee filter sessions 4179afa Allow binlogrouter to be used without a listener 7ad79af Add function for freeing a listener 677a0a2 Move authentication data from services to listeners 4f12af7 Merge remote-tracking branch 'origin/MXS-677' into develop 1419b81 Semi-Sync support to binlog server: code review updtate 0ea0f01 Semi-Sync support to binlog server: added missing routine 4aad909 Semi-Sync support to binlog server b824e1e Add authenticator support to httpd.c 705a688 Change tabs to spaces d0c419e Change method of adding list fields to e.g. DCB 25504fc Document the changed routing priority of hints 41666d1 Remove use_ssl_if_enabled global option a3584e9 Make routing hints have highest priority 34a1d24 Updated document with new binlog router option 01eedc5 Updated documentation with SSL usage 8a4c0f6 Update Replication-Proxy-Binlog-Router-Tutorial.md 4e374aa Update Replication-Proxy-Binlog-Router-Tutorial.md f3f3c57 Update Replication-Proxy-Binlog-Router-Tutorial.md 617b79f Binlog Server: error messages typo fix fa8dfae Binlog Server: error messages review 1b8819c Fix freeing of schemarouter session memory 07f49e1 MXS-788: new code review fix 1fd3b09 MXS-788: show services now displays SSL info 6ca2584 MXS-788 code review fix ae6a7d0 MXS-788 code review 43d3474 Master server SSL connection 90b2377 Use correct variable in listmanager pre-allocation 9a5b238 Fix listmanager pre-allocation 9c78625 Fix a memory leak when backend authentication fails e59a966 Fix hang in list_find_free ff30223 Fix freeing of shared data in schemarouter fc8f9d3 Add missing include in luafilter ecf7f53 Add missing NULL value to filter parameter array 636d849 Update memory allocation approach f0d1d38 Add new allocation functions 97d00a0 Fix writing of uninitialized data to logs e72c9b2 Merge branch 'release-2.0.0' into develop cf2b712 Merge branch 'release-2.0.0' into develop 8917c5c Change the logic behind valid list entry checks c10deff Improve documentation about version_string f59f1f7 Merge branch 'develop' of ssh://github.com/mariadb-corporation/maxscale-new into develop c88edb3 Backend authentication failure improvement abd5bee Revert "Backend authentication failure improvement" 5bb3107 Backend authentication failure improvement b7f434a Add new allocation functions 3f022fa Fix stupid mistake 99c4317 Merge remote-tracking branch 'origin/MXS-677' into develop 3c1ded6 Added connection/authentication failure error reporting in SHOW SLAVE STATUS 0a60f7b Tidy up and deal with review points. ba103ff blr_slave.c: Update strncpy usage 467331e blr_master.c: Strncpy usage updates d2b7c0c Merge remote-tracking branch 'origin/develop-nullauth-merge' into develop 5a8c1d0 qc: Measure execution time at the right place. bccdb93 Merge branch 'NullAuthDeny' into develop 2e6511c Add 5.5.5 prefix to all version strings that lack it 314655a Improve DCB and session initialization and list handling e1c43f0 MXS-655: Make MaxScale logging logrotate(8) compatible ce36afd MXS-626: Don't log a header unless maxlog enabled dcd47a7 blr_file.c: Replace uses of strncpy 6b8f576 bls_slave.c: Replace strncpy with memcpy 68a0039 Add list preallocation, tidy up, simplify init. cb37d1b Fix copyright etc headers. 11a400d Tidy; comment; fix bad copies and mistakes. 7e36ec4 Add list manager files. c4794e3 Initial code for list manager. 1b42e25 Merge remote-tracking branch 'origin/MXS-765' into develop d50f617 Fix problems, extend tests, respond to review. dcb4a91 Filter test folder removed 0b60dbe Add a couple of comments. 83cdba0 Fix overwriting problem. ba5d353 Fix overwriting problem. 53671cb Small fixes in response to review. 173d049 blr.c: Review strncpy usage 4ff6ef2 binlog_common.c: Replace strncpy with memcpy f238e03 maxbinlogcheck.s: Replace strncpy 9807f8d harness: Replace unnecessary use of strncpy 8c7fe6a avro: Modify strncpy usage 9b8008e Small improvements. b7f784f Fix mistakes in testqueuemanager.c cc26962 Restore missing poll.c code; add testqueuemanager.c. 2e91806 Format the filter harness 22059e6 Initial implementation connection queueing. c604dc2 readwritesplit.c: Improve COM_INIT_DB handling 454d920 schemarouter.c: Replace strncpy with strcpy 8e85d66 sharding_common.c: Too long a database name handled explicitly 77f4446 Astyle schemarouter 491f7c2 maxinfo.c: Replace strncpy with memcpy 6b98105 maxinfo: Reformat with astyle c1dbf08 Handle oversize user and database names 5fa4a0f Merge branch 'develop' of ssh://github.com/mariadb-corporation/maxscale-new into develop 706963b BLR_DBUSERS_TAIL new var in blr.h d75b9af Tweak comments, remove trailing blanks. ab2400a Optimise statistics gathering by inline & simpler fns. fb59ddc Remove unnecessary strncpy/strncat usage in Binlog Server bdcd551 resultset.c: Change strncpy to memcpy c6b1c5e Reject rather than cut too long a path 6d8f112 Remove unnecessary strncpy/strncat usage 18bf5ed Remove unnecessary strncpy usage dc0e2db Make maxpasswd more userfriendly c9c8695 Fix calculation of padded_len in encryptPassword 2cfd2c6 dbusers.c: Check strncpy usage 7ab9342 Make more thorough checks in secrets_readKeys be7d593 Format cli.c debugcli.c testroute.c webserver.c 1ee5efb config.c: Check usage of strncpy 3043b12 gq_utils.c: Unnecessary use of strncpy removed 77874ac Add help to maxkeys 38392a3 Update secrets_writeKeys documentation 2d1325c Make SSL optional in MaxScale's own communication bda00da Fix avro build failures b2cb31a Add more OOM macros 41ccf17 Fix strdup usage a48f732 Fix realloc calls 20771f6 Add forgotten extern "C" block 8faf35a Add maxscale allocation functions bb47890 Add macros for OOM logging afea388 Fix silly mistakes. 6dafd22 Make deny default for null auth; move code from common to auth.
1473 lines
47 KiB
C
1473 lines
47 KiB
C
/*
|
|
*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
|
|
*
|
|
* Change Date: 2019-07-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
/**
|
|
* @file mysql_client.c
|
|
*
|
|
* MySQL Protocol module for handling the protocol between the gateway
|
|
* and the client.
|
|
*
|
|
* Revision History
|
|
* Date Who Description
|
|
* 14/06/2013 Mark Riddoch Initial version
|
|
* 17/06/2013 Massimiliano Pinto Added Client To MaxScale routines
|
|
* 24/06/2013 Massimiliano Pinto Added: fetch passwords from service users' hashtable
|
|
* 02/09/2013 Massimiliano Pinto Added: session refcount
|
|
* 16/12/2013 Massimiliano Pinto Added: client closed socket detection with recv(..., MSG_PEEK)
|
|
* 24/02/2014 Massimiliano Pinto Added: on failed authentication a new users' table is loaded
|
|
* with time and frequency limitations
|
|
* If current user is authenticated the new users' table will
|
|
* replace the old one
|
|
* 28/02/2014 Massimiliano Pinto Added: client IPv4 in dcb->ipv4 and inet_ntop for string
|
|
* representation
|
|
* 11/03/2014 Massimiliano Pinto Added: Unix socket support
|
|
* 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake
|
|
* 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path
|
|
* 13/10/2014 Massimiliano Pinto Added: dbname authentication check
|
|
* 10/11/2014 Massimiliano Pinto Added: client charset added to protocol struct
|
|
* 29/05/2015 Markus Makela Added SSL support
|
|
* 11/06/2015 Martin Brampton COM_QUIT suppressed for persistent connections
|
|
* 04/09/2015 Martin Brampton Introduce DUMMY session to fulfill guarantee DCB always has session
|
|
* 09/09/2015 Martin Brampton Modify error handler calls
|
|
* 11/01/2016 Martin Brampton Remove SSL write code, now handled at lower level;
|
|
* replace gwbuf_consume by gwbuf_free (multiple).
|
|
* 07/02/2016 Martin Brampton Split off authentication and SSL.
|
|
* 31/05/2016 Martin Brampton Implement connection throttling
|
|
*/
|
|
#include <gw_protocol.h>
|
|
#include <skygw_utils.h>
|
|
#include <maxscale/alloc.h>
|
|
#include <log_manager.h>
|
|
#include <mysql_client_server_protocol.h>
|
|
#include <mysql_auth.h>
|
|
#include <gw_ssl.h>
|
|
#include <maxscale/poll.h>
|
|
#include <gw.h>
|
|
#include <modinfo.h>
|
|
#include <sys/stat.h>
|
|
#include <modutil.h>
|
|
#include <netinet/tcp.h>
|
|
|
|
#include "gw_authenticator.h"
|
|
|
|
/* @see function load_module in load_utils.c for explanation of the following
|
|
* lint directives.
|
|
*/
|
|
/*lint -e14 */
|
|
MODULE_INFO info =
|
|
{
|
|
MODULE_API_PROTOCOL,
|
|
MODULE_GA,
|
|
GWPROTOCOL_VERSION,
|
|
"The client to MaxScale MySQL protocol implementation"
|
|
};
|
|
/*lint +e14*/
|
|
|
|
static char *version_str = "V1.1.0";
|
|
|
|
static int gw_MySQLAccept(DCB *listener);
|
|
static int gw_MySQLListener(DCB *listener, char *config_bind);
|
|
static int gw_read_client_event(DCB* dcb);
|
|
static int gw_write_client_event(DCB *dcb);
|
|
static int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue);
|
|
static int gw_error_client_event(DCB *dcb);
|
|
static int gw_client_close(DCB *dcb);
|
|
static int gw_client_hangup_event(DCB *dcb);
|
|
static char *gw_default_auth();
|
|
static int gw_connection_limit(DCB *dcb, int limit);
|
|
static int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
|
|
static int MySQLSendHandshake(DCB* dcb);
|
|
static int route_by_statement(SESSION *, GWBUF **);
|
|
static void mysql_client_auth_error_handling(DCB *dcb, int auth_val);
|
|
static int gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
|
|
static int gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
|
|
static int gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint8_t capabilities);
|
|
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int);
|
|
static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read);
|
|
static void gw_process_one_new_client(DCB *client_dcb);
|
|
|
|
/*
|
|
* The "module object" for the mysqld client protocol module.
|
|
*/
|
|
static GWPROTOCOL MyObject =
|
|
{
|
|
gw_read_client_event, /* Read - EPOLLIN handler */
|
|
gw_MySQLWrite_client, /* Write - data from gateway */
|
|
gw_write_client_event, /* WriteReady - EPOLLOUT handler */
|
|
gw_error_client_event, /* Error - EPOLLERR handler */
|
|
gw_client_hangup_event, /* HangUp - EPOLLHUP handler */
|
|
gw_MySQLAccept, /* Accept */
|
|
NULL, /* Connect */
|
|
gw_client_close, /* Close */
|
|
gw_MySQLListener, /* Listen */
|
|
NULL, /* Authentication */
|
|
NULL, /* Session */
|
|
gw_default_auth, /* Default authenticator */
|
|
gw_connection_limit /* Send error connection limit */
|
|
};
|
|
|
|
/**
|
|
* Implementation of the mandatory version entry point
|
|
*
|
|
* @return version string of the module
|
|
*
|
|
* @see function load_module in load_utils.c for explanation of the following
|
|
* lint directives.
|
|
*/
|
|
/*lint -e14 */
|
|
char* version()
|
|
{
|
|
return version_str;
|
|
}
|
|
|
|
/**
|
|
* The module initialisation routine, called when the module
|
|
* is first loaded.
|
|
*/
|
|
void ModuleInit()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* The module entry point routine. It is this routine that
|
|
* must populate the structure that is referred to as the
|
|
* "module object", this is a structure with the set of
|
|
* external entry points for this module.
|
|
*
|
|
* @return The module object
|
|
*/
|
|
GWPROTOCOL* GetModuleObject()
|
|
{
|
|
return &MyObject;
|
|
}
|
|
/*lint +e14 */
|
|
|
|
/**
|
|
* The default authenticator name for this protocol
|
|
*
|
|
* @return name of authenticator
|
|
*/
|
|
static char *gw_default_auth()
|
|
{
|
|
return "MySQLAuth";
|
|
}
|
|
/**
|
|
* mysql_send_ok
|
|
*
|
|
* Send a MySQL protocol OK message to the dcb (client)
|
|
*
|
|
* @param dcb Descriptor Control Block for the connection to which the OK is sent
|
|
* @param packet_number
|
|
* @param in_affected_rows
|
|
* @param mysql_message
|
|
* @return packet length
|
|
*
|
|
*/
|
|
int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message)
|
|
{
|
|
uint8_t *outbuf = NULL;
|
|
uint32_t mysql_payload_size = 0;
|
|
uint8_t mysql_packet_header[4];
|
|
uint8_t *mysql_payload = NULL;
|
|
uint8_t field_count = 0;
|
|
uint8_t affected_rows = 0;
|
|
uint8_t insert_id = 0;
|
|
uint8_t mysql_server_status[2];
|
|
uint8_t mysql_warning_counter[2];
|
|
GWBUF *buf;
|
|
|
|
affected_rows = in_affected_rows;
|
|
|
|
mysql_payload_size =
|
|
sizeof(field_count) +
|
|
sizeof(affected_rows) +
|
|
sizeof(insert_id) +
|
|
sizeof(mysql_server_status) +
|
|
sizeof(mysql_warning_counter);
|
|
|
|
if (mysql_message != NULL)
|
|
{
|
|
mysql_payload_size += strlen(mysql_message);
|
|
}
|
|
|
|
// allocate memory for packet header + payload
|
|
if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
outbuf = GWBUF_DATA(buf);
|
|
|
|
// write packet header with packet number
|
|
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
|
|
mysql_packet_header[3] = packet_number;
|
|
|
|
// write header
|
|
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
|
|
|
|
mysql_payload = outbuf + sizeof(mysql_packet_header);
|
|
|
|
mysql_server_status[0] = 2;
|
|
mysql_server_status[1] = 0;
|
|
mysql_warning_counter[0] = 0;
|
|
mysql_warning_counter[1] = 0;
|
|
|
|
// write data
|
|
memcpy(mysql_payload, &field_count, sizeof(field_count));
|
|
mysql_payload = mysql_payload + sizeof(field_count);
|
|
|
|
memcpy(mysql_payload, &affected_rows, sizeof(affected_rows));
|
|
mysql_payload = mysql_payload + sizeof(affected_rows);
|
|
|
|
memcpy(mysql_payload, &insert_id, sizeof(insert_id));
|
|
mysql_payload = mysql_payload + sizeof(insert_id);
|
|
|
|
memcpy(mysql_payload, mysql_server_status, sizeof(mysql_server_status));
|
|
mysql_payload = mysql_payload + sizeof(mysql_server_status);
|
|
|
|
memcpy(mysql_payload, mysql_warning_counter, sizeof(mysql_warning_counter));
|
|
mysql_payload = mysql_payload + sizeof(mysql_warning_counter);
|
|
|
|
if (mysql_message != NULL)
|
|
{
|
|
memcpy(mysql_payload, mysql_message, strlen(mysql_message));
|
|
}
|
|
|
|
// writing data in the Client buffer queue
|
|
dcb->func.write(dcb, buf);
|
|
|
|
return sizeof(mysql_packet_header) + mysql_payload_size;
|
|
}
|
|
|
|
/**
|
|
* MySQLSendHandshake
|
|
*
|
|
* @param dcb The descriptor control block to use for sending the handshake request
|
|
* @return The packet length sent
|
|
*/
|
|
int MySQLSendHandshake(DCB* dcb)
|
|
{
|
|
uint8_t *outbuf = NULL;
|
|
uint32_t mysql_payload_size = 0;
|
|
uint8_t mysql_packet_header[4];
|
|
uint8_t mysql_packet_id = 0;
|
|
/* uint8_t mysql_filler = GW_MYSQL_HANDSHAKE_FILLER; not needed*/
|
|
uint8_t mysql_protocol_version = GW_MYSQL_PROTOCOL_VERSION;
|
|
uint8_t *mysql_handshake_payload = NULL;
|
|
uint8_t mysql_thread_id_num[4];
|
|
uint8_t mysql_scramble_buf[9] = "";
|
|
uint8_t mysql_plugin_data[13] = "";
|
|
uint8_t mysql_server_capabilities_one[2];
|
|
uint8_t mysql_server_capabilities_two[2];
|
|
uint8_t mysql_server_language = 8;
|
|
uint8_t mysql_server_status[2];
|
|
uint8_t mysql_scramble_len = 21;
|
|
uint8_t mysql_filler_ten[10];
|
|
/* uint8_t mysql_last_byte = 0x00; not needed */
|
|
char server_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1]="";
|
|
char *version_string;
|
|
int len_version_string = 0;
|
|
int id_num;
|
|
|
|
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
|
|
GWBUF *buf;
|
|
|
|
/* get the version string from service property if available*/
|
|
if (dcb->service->version_string != NULL)
|
|
{
|
|
version_string = dcb->service->version_string;
|
|
len_version_string = strlen(version_string);
|
|
}
|
|
else
|
|
{
|
|
version_string = GW_MYSQL_VERSION;
|
|
len_version_string = strlen(GW_MYSQL_VERSION);
|
|
}
|
|
|
|
gw_generate_random_str(server_scramble, GW_MYSQL_SCRAMBLE_SIZE);
|
|
|
|
// copy back to the caller
|
|
memcpy(protocol->scramble, server_scramble, GW_MYSQL_SCRAMBLE_SIZE);
|
|
|
|
// fill the handshake packet
|
|
|
|
memset(mysql_filler_ten, 0x00, sizeof(mysql_filler_ten));
|
|
|
|
// thread id, now put thePID
|
|
id_num = getpid() + dcb->fd;
|
|
gw_mysql_set_byte4(mysql_thread_id_num, id_num);
|
|
|
|
memcpy(mysql_scramble_buf, server_scramble, 8);
|
|
|
|
memcpy(mysql_plugin_data, server_scramble + 8, 12);
|
|
|
|
mysql_payload_size =
|
|
sizeof(mysql_protocol_version) + (len_version_string + 1) + sizeof(mysql_thread_id_num) + 8 +
|
|
sizeof(/* mysql_filler */ uint8_t) + sizeof(mysql_server_capabilities_one) + sizeof(mysql_server_language) +
|
|
sizeof(mysql_server_status) + sizeof(mysql_server_capabilities_two) + sizeof(mysql_scramble_len) +
|
|
sizeof(mysql_filler_ten) + 12 + sizeof(/* mysql_last_byte */ uint8_t) + strlen("mysql_native_password") +
|
|
sizeof(/* mysql_last_byte */ uint8_t);
|
|
|
|
// allocate memory for packet header + payload
|
|
if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
|
|
{
|
|
ss_dassert(buf != NULL);
|
|
return 0;
|
|
}
|
|
outbuf = GWBUF_DATA(buf);
|
|
|
|
// write packet header with mysql_payload_size
|
|
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
|
|
|
|
// write packet number, now is 0
|
|
mysql_packet_header[3]= mysql_packet_id;
|
|
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
|
|
|
|
// current buffer pointer
|
|
mysql_handshake_payload = outbuf + sizeof(mysql_packet_header);
|
|
|
|
// write protocol version
|
|
memcpy(mysql_handshake_payload, &mysql_protocol_version, sizeof(mysql_protocol_version));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_protocol_version);
|
|
|
|
// write server version plus 0 filler
|
|
strcpy((char *)mysql_handshake_payload, version_string);
|
|
mysql_handshake_payload = mysql_handshake_payload + len_version_string;
|
|
|
|
*mysql_handshake_payload = 0x00;
|
|
|
|
mysql_handshake_payload++;
|
|
|
|
// write thread id
|
|
memcpy(mysql_handshake_payload, mysql_thread_id_num, sizeof(mysql_thread_id_num));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_thread_id_num);
|
|
|
|
// write scramble buf
|
|
memcpy(mysql_handshake_payload, mysql_scramble_buf, 8);
|
|
mysql_handshake_payload = mysql_handshake_payload + 8;
|
|
*mysql_handshake_payload = GW_MYSQL_HANDSHAKE_FILLER;
|
|
mysql_handshake_payload++;
|
|
|
|
// write server capabilities part one
|
|
mysql_server_capabilities_one[0] = GW_MYSQL_SERVER_CAPABILITIES_BYTE1;
|
|
mysql_server_capabilities_one[1] = GW_MYSQL_SERVER_CAPABILITIES_BYTE2;
|
|
|
|
|
|
mysql_server_capabilities_one[0] &= ~(int)GW_MYSQL_CAPABILITIES_COMPRESS;
|
|
|
|
if (ssl_required_by_dcb(dcb))
|
|
{
|
|
mysql_server_capabilities_one[1] |= (int)GW_MYSQL_CAPABILITIES_SSL >> 8;
|
|
}
|
|
|
|
memcpy(mysql_handshake_payload, mysql_server_capabilities_one, sizeof(mysql_server_capabilities_one));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_one);
|
|
|
|
// write server language
|
|
memcpy(mysql_handshake_payload, &mysql_server_language, sizeof(mysql_server_language));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_language);
|
|
|
|
//write server status
|
|
mysql_server_status[0] = 2;
|
|
mysql_server_status[1] = 0;
|
|
memcpy(mysql_handshake_payload, mysql_server_status, sizeof(mysql_server_status));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_status);
|
|
|
|
//write server capabilities part two
|
|
mysql_server_capabilities_two[0] = 15;
|
|
mysql_server_capabilities_two[1] = 128;
|
|
|
|
memcpy(mysql_handshake_payload, mysql_server_capabilities_two, sizeof(mysql_server_capabilities_two));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_two);
|
|
|
|
// write scramble_len
|
|
memcpy(mysql_handshake_payload, &mysql_scramble_len, sizeof(mysql_scramble_len));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_scramble_len);
|
|
|
|
//write 10 filler
|
|
memcpy(mysql_handshake_payload, mysql_filler_ten, sizeof(mysql_filler_ten));
|
|
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_filler_ten);
|
|
|
|
// write plugin data
|
|
memcpy(mysql_handshake_payload, mysql_plugin_data, 12);
|
|
mysql_handshake_payload = mysql_handshake_payload + 12;
|
|
|
|
//write last byte, 0
|
|
*mysql_handshake_payload = 0x00;
|
|
mysql_handshake_payload++;
|
|
|
|
// to be understanded ????
|
|
memcpy(mysql_handshake_payload, "mysql_native_password", strlen("mysql_native_password"));
|
|
mysql_handshake_payload = mysql_handshake_payload + strlen("mysql_native_password");
|
|
|
|
//write last byte, 0
|
|
*mysql_handshake_payload = 0x00;
|
|
|
|
// writing data in the Client buffer queue
|
|
dcb->func.write(dcb, buf);
|
|
|
|
return sizeof(mysql_packet_header) + mysql_payload_size;
|
|
}
|
|
|
|
/**
|
|
* Write function for client DCB: writes data from MaxScale to Client
|
|
*
|
|
* @param dcb The DCB of the client
|
|
* @param queue Queue of buffers to write
|
|
*/
|
|
int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue)
|
|
{
|
|
return dcb_write(dcb, queue);
|
|
}
|
|
|
|
/**
|
|
* @brief Client read event triggered by EPOLLIN
|
|
*
|
|
* @param dcb Descriptor control block
|
|
* @return 0 if succeed, 1 otherwise
|
|
*/
|
|
int gw_read_client_event(DCB* dcb)
|
|
{
|
|
MySQLProtocol *protocol;
|
|
GWBUF *read_buffer = NULL;
|
|
int return_code = 0;
|
|
int nbytes_read = 0;
|
|
int max_bytes = 0;
|
|
|
|
CHK_DCB(dcb);
|
|
if (dcb->dcb_role != DCB_ROLE_CLIENT_HANDLER)
|
|
{
|
|
MXS_ERROR("DCB must be a client handler for MySQL client protocol.");
|
|
return 1;
|
|
}
|
|
|
|
protocol = (MySQLProtocol *)dcb->protocol;
|
|
CHK_PROTOCOL(protocol);
|
|
|
|
#ifdef SS_DEBUG
|
|
MXS_DEBUG("[gw_read_client_event] Protocol state: %s",
|
|
gw_mysql_protocol_state2string(protocol->protocol_auth_state));
|
|
|
|
#endif
|
|
|
|
/**
|
|
* The use of max_bytes seems like a hack, but no better option is available
|
|
* at the time of writing. When a MySQL server receives a new connection
|
|
* request, it sends an Initial Handshake Packet. Where the client wants to
|
|
* use SSL, it responds with an SSL Request Packet (in place of a Handshake
|
|
* Response Packet). The SSL Request Packet contains only the basic header,
|
|
* and not the user credentials. It is 36 bytes long. The server then
|
|
* initiates the SSL handshake (via calls to OpenSSL).
|
|
*
|
|
* In many cases, this is what happens. But occasionally, the client seems
|
|
* to send a packet much larger than 36 bytes (in tests it was 333 bytes).
|
|
* If the whole of the packet is read, it is then lost to the SSL handshake
|
|
* process. Why this happens is presently unknown. Reading just 36 bytes
|
|
* when the server requires SSL and SSL has not yet been negotiated seems
|
|
* to solve the problem.
|
|
*
|
|
* If a neater solution can be found, so much the better.
|
|
*/
|
|
if (ssl_required_but_not_negotiated(dcb))
|
|
{
|
|
max_bytes = 36;
|
|
}
|
|
return_code = dcb_read(dcb, &read_buffer, max_bytes);
|
|
if (return_code < 0)
|
|
{
|
|
dcb_close(dcb);
|
|
}
|
|
if (0 == (nbytes_read = gwbuf_length(read_buffer)))
|
|
{
|
|
return return_code;
|
|
}
|
|
|
|
return_code = 0;
|
|
|
|
switch (protocol->protocol_auth_state)
|
|
{
|
|
/**
|
|
*
|
|
* When a listener receives a new connection request, it creates a
|
|
* request handler DCB to for the client connection. The listener also
|
|
* sends the initial authentication request to the client. The first
|
|
* time this function is called from the poll loop, the client reply
|
|
* to the authentication request should be available.
|
|
*
|
|
* If the authentication is successful the protocol authentication state
|
|
* will be changed to MYSQL_IDLE (see below).
|
|
*
|
|
*/
|
|
case MYSQL_AUTH_SENT:
|
|
/* After this call read_buffer will point to freed data */
|
|
if (nbytes_read < 3 || (0 == max_bytes && nbytes_read <
|
|
(MYSQL_GET_PACKET_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4)) ||
|
|
(0 != max_bytes && nbytes_read < max_bytes))
|
|
{
|
|
spinlock_acquire(&dcb->authlock);
|
|
dcb->dcb_readqueue = read_buffer;
|
|
spinlock_release(&dcb->authlock);
|
|
return 0;
|
|
}
|
|
return_code = gw_read_do_authentication(dcb, read_buffer, nbytes_read);
|
|
break;
|
|
|
|
/**
|
|
*
|
|
* Once a client connection is authenticated, the protocol authentication
|
|
* state will be MYSQL_IDLE and so every event of data received will
|
|
* result in a call that comes to this section of code.
|
|
*
|
|
*/
|
|
case MYSQL_IDLE:
|
|
/* After this call read_buffer will point to freed data */
|
|
return_code = gw_read_normal_data(dcb, read_buffer, nbytes_read);
|
|
break;
|
|
|
|
case MYSQL_AUTH_FAILED:
|
|
gwbuf_free(read_buffer);
|
|
return_code = 1;
|
|
break;
|
|
|
|
default:
|
|
MXS_ERROR("In mysql_client.c unexpected protocol authentication state");
|
|
break;
|
|
}
|
|
|
|
return return_code;
|
|
}
|
|
|
|
/**
|
|
* @brief Client read event, process when client not yet authenticated
|
|
*
|
|
* @param dcb Descriptor control block
|
|
* @param read_buffer A buffer containing the data read from client
|
|
* @param nbytes_read The number of bytes of data read
|
|
* @return 0 if succeed, 1 otherwise
|
|
*/
|
|
static int
|
|
gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
|
{
|
|
MySQLProtocol *protocol;
|
|
int auth_val;
|
|
|
|
protocol = (MySQLProtocol *)dcb->protocol;
|
|
/* int compress = -1; */
|
|
|
|
/**
|
|
* The first step in the authentication process is to extract the
|
|
* relevant information from the buffer supplied and place it
|
|
* into a data structure pointed to by the DCB. The "success"
|
|
* result is not final, it implies only that the process is so
|
|
* far successful, not that authentication has completed. If the
|
|
* data extraction succeeds, then a call is made to the actual
|
|
* authenticate function to carry out the user checks.
|
|
*/
|
|
if (MYSQL_AUTH_SUCCEEDED == (
|
|
auth_val = dcb->authfunc.extract(dcb, read_buffer)))
|
|
{
|
|
/*
|
|
* Maybe this comment will be useful some day:
|
|
compress =
|
|
GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4(
|
|
&protocol->client_capabilities);
|
|
*/
|
|
auth_val = dcb->authfunc.authenticate(dcb);
|
|
}
|
|
|
|
/**
|
|
* At this point, if the auth_val return code indicates success
|
|
* the user authentication has been successfully completed.
|
|
* But in order to have a working connection, a session has to
|
|
* be created. Provided that is also successful (indicated by a
|
|
* non-null session) then the whole process has succeeded. In all
|
|
* other cases an error return is made.
|
|
*/
|
|
if (MYSQL_AUTH_SUCCEEDED == auth_val)
|
|
{
|
|
SESSION *session;
|
|
|
|
protocol->protocol_auth_state = MYSQL_AUTH_RECV;
|
|
/**
|
|
* Create session, and a router session for it.
|
|
* If successful, there will be backend connection(s)
|
|
* after this point. The protocol authentication state
|
|
* is changed so that future data will go through the
|
|
* normal data handling function instead of this one.
|
|
*/
|
|
session = session_alloc(dcb->service, dcb);
|
|
|
|
if (session != NULL)
|
|
{
|
|
int packet_number = ssl_required_by_dcb(dcb) ? 3 : 2;
|
|
|
|
CHK_SESSION(session);
|
|
ss_dassert(session->state != SESSION_STATE_ALLOC &&
|
|
session->state != SESSION_STATE_DUMMY);
|
|
|
|
protocol->protocol_auth_state = MYSQL_IDLE;
|
|
/**
|
|
* Send an AUTH_OK packet to the client,
|
|
* packet sequence is # packet_number
|
|
*/
|
|
mysql_send_ok(dcb, packet_number, 0, NULL);
|
|
}
|
|
else
|
|
{
|
|
auth_val = MYSQL_AUTH_NO_SESSION;
|
|
}
|
|
}
|
|
/**
|
|
* If we did not get success throughout, then the protocol state is updated,
|
|
* the client is notified of the failure and the DCB is closed.
|
|
*/
|
|
if (MYSQL_AUTH_SUCCEEDED != auth_val && MYSQL_AUTH_SSL_INCOMPLETE != auth_val)
|
|
{
|
|
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
|
|
mysql_client_auth_error_handling(dcb, auth_val);
|
|
/**
|
|
* Close DCB and which will release MYSQL_session
|
|
*/
|
|
dcb_close(dcb);
|
|
}
|
|
/* One way or another, the buffer is now fully processed */
|
|
gwbuf_free(read_buffer);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Helper function to split and store the buffer
|
|
* @param client_dcb Client DCB
|
|
* @param queue Buffer to split
|
|
* @param offset Offset where the split is made
|
|
* @return The first part of the buffer
|
|
*/
|
|
static GWBUF* split_and_store(DCB *client_dcb, GWBUF* queue, int offset)
|
|
{
|
|
GWBUF* newbuf = gwbuf_split(&queue, offset);
|
|
dcb_append_readqueue(client_dcb, queue);
|
|
return newbuf;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if the DCB is idle from the protocol's point of view
|
|
*
|
|
* This checks if all expected data from the DCB has been read. The values
|
|
* prefixed with @c protocol_ should be manipulated by the protocol modules.
|
|
*
|
|
* @param dcb DCB to check
|
|
* @return True if the DCB protocol is not expecting any data
|
|
*/
|
|
static bool protocol_is_idle(DCB *dcb)
|
|
{
|
|
return dcb->protocol_bytes_processed == dcb->protocol_packet_length;
|
|
}
|
|
|
|
/**
|
|
* @brief Process the commands the client is executing
|
|
*
|
|
* The data read from the network is not guaranteed to contain a complete MySQL
|
|
* packet. This means that it is possible that a command sent by the client is
|
|
* split across multiple network packets and those packets need to be processed
|
|
* individually.
|
|
*
|
|
* The forwarding of the data to the routers starts once the length and command
|
|
* bytes have been read. The @c current_command field of the protocol
|
|
* structure is guaranteed to always represent the current command being executed
|
|
* by the client.
|
|
*
|
|
* Currently the gathered information is used by the readconnroute module to
|
|
* detect COM_CHANGE_USER packets.
|
|
*
|
|
* @param dcb Client MySQL protocol struct
|
|
* @param bytes_available Number of bytes available
|
|
* @param queue Data written by the client
|
|
* @return True if routing can proceed, false if processing should be attempted
|
|
* later when more data is available
|
|
*/
|
|
static bool process_client_commands(DCB* dcb, int bytes_available, GWBUF** buffer)
|
|
{
|
|
GWBUF* queue = *buffer;
|
|
|
|
/** Make sure we have enough data if the client is sending a new command */
|
|
if (protocol_is_idle(dcb) && bytes_available < MYSQL_HEADER_LEN)
|
|
{
|
|
dcb_append_readqueue(dcb, queue);
|
|
return false;
|
|
}
|
|
|
|
int offset = 0;
|
|
|
|
while (bytes_available)
|
|
{
|
|
if (protocol_is_idle(dcb))
|
|
{
|
|
int pktlen;
|
|
uint8_t cmd = (uint8_t)MYSQL_COM_QUERY; // Treat empty packets as COM_QUERY
|
|
|
|
/**
|
|
* Buffer has at least 5 bytes, the packet is in contiguous memory
|
|
* and it's the first packet in the buffer.
|
|
*/
|
|
if (offset == 0 && GWBUF_LENGTH(queue) >= MYSQL_HEADER_LEN + 1)
|
|
{
|
|
uint8_t *data = (uint8_t*)GWBUF_DATA(queue);
|
|
pktlen = gw_mysql_get_byte3(data);
|
|
if (pktlen)
|
|
{
|
|
cmd = *(data + MYSQL_HEADER_LEN);
|
|
}
|
|
}
|
|
/**
|
|
* We have more than one packet in the buffer or the first 5 bytes
|
|
* of a packet are split across two buffers.
|
|
*/
|
|
else
|
|
{
|
|
uint8_t packet_header[MYSQL_HEADER_LEN];
|
|
|
|
if (gwbuf_copy_data(queue, offset, MYSQL_HEADER_LEN, packet_header) != MYSQL_HEADER_LEN)
|
|
{
|
|
ss_dassert(offset > 0);
|
|
queue = split_and_store(dcb, queue, offset);
|
|
break;
|
|
}
|
|
|
|
pktlen = gw_mysql_get_byte3(packet_header);
|
|
|
|
/**
|
|
* Check if the packet is empty, and if not, if we have the command byte.
|
|
* If we an empty packet or have at least 5 bytes of data, we can start
|
|
* sending the data to the router.
|
|
*/
|
|
if (pktlen && gwbuf_copy_data(queue, MYSQL_HEADER_LEN, 1, &cmd) != 1)
|
|
{
|
|
if ((queue = split_and_store(dcb, queue, offset)) == NULL)
|
|
{
|
|
ss_dassert(bytes_available == MYSQL_HEADER_LEN);
|
|
return false;
|
|
}
|
|
ss_dassert(offset > 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
|
proto->current_command = cmd;
|
|
dcb->protocol_packet_length = pktlen + MYSQL_HEADER_LEN;
|
|
dcb->protocol_bytes_processed = 0;
|
|
}
|
|
|
|
int bytes_needed = dcb->protocol_packet_length - dcb->protocol_bytes_processed;
|
|
int packet_bytes = bytes_needed <= bytes_available ? bytes_needed : bytes_available;
|
|
|
|
bytes_available -= packet_bytes;
|
|
dcb->protocol_bytes_processed += packet_bytes;
|
|
offset += packet_bytes;
|
|
ss_dassert(dcb->protocol_bytes_processed <= dcb->protocol_packet_length);
|
|
}
|
|
|
|
ss_dassert(bytes_available >= 0);
|
|
ss_dassert(queue);
|
|
*buffer = queue;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Client read event, process data, client already authenticated
|
|
*
|
|
* First do some checks and get the router capabilities. If the router
|
|
* wants to process each individual statement, then the data must be split
|
|
* into individual SQL statements. Any data that is left over is held in the
|
|
* DCB read queue.
|
|
*
|
|
* Finally, the general client data processing function is called.
|
|
*
|
|
* @param dcb Descriptor control block
|
|
* @param read_buffer A buffer containing the data read from client
|
|
* @param nbytes_read The number of bytes of data read
|
|
* @return 0 if succeed, 1 otherwise
|
|
*/
|
|
static int
|
|
gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
|
{
|
|
SESSION *session;
|
|
session_state_t session_state_value;
|
|
uint8_t capabilities = 0;
|
|
|
|
session = dcb->session;
|
|
CHK_SESSION(session);
|
|
session_state_value = session->state;
|
|
if (session_state_value != SESSION_STATE_ROUTER_READY)
|
|
{
|
|
if (session_state_value != SESSION_STATE_STOPPING)
|
|
{
|
|
MXS_ERROR("Session received a query in incorrect state %s",
|
|
STRSESSIONSTATE(session_state_value));
|
|
}
|
|
gwbuf_free(read_buffer);
|
|
dcb_close(dcb);
|
|
return 1;
|
|
}
|
|
|
|
/** Ask what type of input the router expects */
|
|
capabilities = session->service->router->getCapabilities(
|
|
session->service->router_instance, session->router_session);
|
|
|
|
/** Update the current protocol command being executed */
|
|
if (!process_client_commands(dcb, nbytes_read, &read_buffer))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/** If the router requires statement input or we are still authenticating
|
|
* we need to make sure that a complete SQL packet is read before continuing */
|
|
if (capabilities & (int)RCAP_TYPE_STMT_INPUT)
|
|
{
|
|
uint8_t* data;
|
|
int packet_size;
|
|
|
|
if (nbytes_read < 3 || nbytes_read <
|
|
(MYSQL_GET_PACKET_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4))
|
|
{
|
|
spinlock_acquire(&dcb->authlock);
|
|
dcb->dcb_readqueue = read_buffer;
|
|
spinlock_release(&dcb->authlock);
|
|
return 0;
|
|
}
|
|
gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL);
|
|
}
|
|
return gw_read_finish_processing(dcb, read_buffer, capabilities);
|
|
}
|
|
|
|
/**
|
|
* @brief Client read event, common processing after single statement handling
|
|
*
|
|
* @param dcb Descriptor control block
|
|
* @param read_buffer A buffer containing the data read from client
|
|
* @param capabilities The router capabilities flags
|
|
* @return 0 if succeed, 1 otherwise
|
|
*/
|
|
static int
|
|
gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint8_t capabilities)
|
|
{
|
|
SESSION *session = dcb->session;
|
|
uint8_t *payload = GWBUF_DATA(read_buffer);
|
|
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
|
CHK_PROTOCOL(proto)
|
|
int return_code = 0;
|
|
|
|
/* Now, we are assuming in the first buffer there is
|
|
* the information for mysql command */
|
|
/* Route COM_QUIT to backend */
|
|
if (proto->current_command == MYSQL_COM_QUIT)
|
|
{
|
|
/**
|
|
* Sends COM_QUIT packets since buffer is already
|
|
* created. A BREF_CLOSED flag is set so dcb_close won't
|
|
* send redundant COM_QUIT.
|
|
*/
|
|
/* Temporarily suppressed: SESSION_ROUTE_QUERY(session, read_buffer); */
|
|
/* Replaced with freeing the read buffer. */
|
|
gwbuf_free(read_buffer);
|
|
/**
|
|
* Close router session which causes closing of backends.
|
|
*/
|
|
dcb_close(dcb);
|
|
}
|
|
else
|
|
{
|
|
/** Reset error handler when routing of the new query begins */
|
|
dcb->dcb_errhandle_called = false;
|
|
|
|
if (capabilities & (int)RCAP_TYPE_STMT_INPUT)
|
|
{
|
|
/**
|
|
* Feed each statement completely and separately
|
|
* to router. The routing functions return 1 for
|
|
* success or 0 for failure.
|
|
*/
|
|
return_code = route_by_statement(session, &read_buffer) ? 0 : 1;
|
|
|
|
if (read_buffer != NULL)
|
|
{
|
|
/* Must have been data left over */
|
|
/* Add incomplete mysql packet to read queue */
|
|
spinlock_acquire(&dcb->authlock);
|
|
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer);
|
|
spinlock_release(&dcb->authlock);
|
|
}
|
|
}
|
|
else if (NULL != session->router_session || (capabilities & (int)RCAP_TYPE_NO_RSESSION))
|
|
{
|
|
/** Feed whole packet to router, which will free it
|
|
* and return 1 for success, 0 for failure
|
|
*/
|
|
return_code = SESSION_ROUTE_QUERY(session, read_buffer) ? 0 : 1;
|
|
}
|
|
/* else return_code is still 0 from when it was originally set */
|
|
/* Note that read_buffer has been freed or transferred by this point */
|
|
|
|
/** Routing failed */
|
|
if (return_code != 0)
|
|
{
|
|
bool router_can_continue;
|
|
GWBUF* errbuf;
|
|
/**
|
|
* Create error to be sent to client if session
|
|
* can't be continued.
|
|
*/
|
|
errbuf = mysql_create_custom_error(1, 0,
|
|
"Routing failed. Session is closed.");
|
|
/**
|
|
* Ensure that there are enough backends
|
|
* available for router to continue operation.
|
|
*/
|
|
session->service->router->handleError(session->service->router_instance,
|
|
session->router_session,
|
|
errbuf,
|
|
dcb,
|
|
ERRACT_NEW_CONNECTION,
|
|
&router_can_continue);
|
|
gwbuf_free(errbuf);
|
|
/**
|
|
* If the router cannot continue, close session
|
|
*/
|
|
if (!router_can_continue)
|
|
{
|
|
MXS_ERROR("Routing the query failed. "
|
|
"Session will be closed.");
|
|
}
|
|
}
|
|
}
|
|
return return_code;
|
|
}
|
|
|
|
/**
|
|
* @brief Analyse authentication errors and write appropriate log messages
|
|
*
|
|
* @param dcb Request handler DCB connected to the client
|
|
* @param auth_val The type of authentication failure
|
|
* @note Authentication status codes are defined in mysql_client_server_protocol.h
|
|
*/
|
|
static void
|
|
mysql_client_auth_error_handling(DCB *dcb, int auth_val)
|
|
{
|
|
int packet_number, message_len;
|
|
char *fail_str = NULL;
|
|
|
|
packet_number = ssl_required_by_dcb(dcb) ? 3 : 2;
|
|
|
|
switch (auth_val)
|
|
{
|
|
case MYSQL_AUTH_NO_SESSION:
|
|
MXS_DEBUG("%lu [gw_read_client_event] session "
|
|
"creation failed. fd %d, "
|
|
"state = MYSQL_AUTH_NO_SESSION.",
|
|
pthread_self(),
|
|
dcb->fd);
|
|
|
|
/** Send ERR 1045 to client */
|
|
mysql_send_auth_error(dcb,
|
|
packet_number,
|
|
0,
|
|
"failed to create new session");
|
|
break;
|
|
case MYSQL_FAILED_AUTH_DB:
|
|
MXS_DEBUG("%lu [gw_read_client_event] database "
|
|
"specified was not valid. fd %d, "
|
|
"state = MYSQL_FAILED_AUTH_DB.",
|
|
pthread_self(),
|
|
dcb->fd);
|
|
/** Send error 1049 to client */
|
|
message_len = 25 + MYSQL_DATABASE_MAXLEN;
|
|
|
|
fail_str = MXS_CALLOC(1, message_len+1);
|
|
MXS_ABORT_IF_NULL(fail_str);
|
|
snprintf(fail_str, message_len, "Unknown database '%s'",
|
|
(char*)((MYSQL_session *)dcb->data)->db);
|
|
|
|
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str);
|
|
break;
|
|
case MYSQL_FAILED_AUTH_SSL:
|
|
MXS_DEBUG("%lu [gw_read_client_event] client is "
|
|
"not SSL capable for SSL listener. fd %d, "
|
|
"state = MYSQL_FAILED_AUTH_SSL.",
|
|
pthread_self(),
|
|
dcb->fd);
|
|
|
|
/** Send ERR 1045 to client */
|
|
mysql_send_auth_error(dcb,
|
|
packet_number,
|
|
0,
|
|
"failed to complete SSL authentication");
|
|
break;
|
|
case MYSQL_AUTH_SSL_INCOMPLETE:
|
|
MXS_DEBUG("%lu [gw_read_client_event] unable to "
|
|
"complete SSL authentication. fd %d, "
|
|
"state = MYSQL_AUTH_SSL_INCOMPLETE.",
|
|
pthread_self(),
|
|
dcb->fd);
|
|
|
|
/** Send ERR 1045 to client */
|
|
mysql_send_auth_error(dcb,
|
|
packet_number,
|
|
0,
|
|
"failed to complete SSL authentication");
|
|
break;
|
|
case MYSQL_FAILED_AUTH:
|
|
MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, "
|
|
"state = MYSQL_FAILED_AUTH.",
|
|
pthread_self(),
|
|
dcb->fd);
|
|
/** Send error 1045 to client */
|
|
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
|
|
dcb->remote,
|
|
(char*)((MYSQL_session *)dcb->data)->client_sha1,
|
|
(char*)((MYSQL_session *)dcb->data)->db, auth_val);
|
|
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
|
|
break;
|
|
default:
|
|
MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, "
|
|
"state unrecognized.",
|
|
pthread_self(),
|
|
dcb->fd);
|
|
/** Send error 1045 to client */
|
|
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
|
|
dcb->remote,
|
|
(char*)((MYSQL_session *)dcb->data)->client_sha1,
|
|
(char*)((MYSQL_session *)dcb->data)->db, auth_val);
|
|
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
|
|
}
|
|
MXS_FREE(fail_str);
|
|
}
|
|
|
|
static int
|
|
gw_connection_limit(DCB *dcb, int limit)
|
|
{
|
|
return mysql_send_standard_error(dcb, 0, 1040, "Too many connections");
|
|
}
|
|
///////////////////////////////////////////////
|
|
// client write event to Client triggered by EPOLLOUT
|
|
//////////////////////////////////////////////
|
|
/**
|
|
* @node Client's fd became writable, and EPOLLOUT event
|
|
* arrived. As a consequence, client input buffer (writeq) is flushed.
|
|
*
|
|
* Parameters:
|
|
* @param dcb - in, use
|
|
* client dcb
|
|
*
|
|
* @return constantly 1
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
int gw_write_client_event(DCB *dcb)
|
|
{
|
|
MySQLProtocol *protocol = NULL;
|
|
|
|
CHK_DCB(dcb);
|
|
|
|
ss_dassert(dcb->state != DCB_STATE_DISCONNECTED);
|
|
|
|
if (dcb == NULL)
|
|
{
|
|
goto return_1;
|
|
}
|
|
|
|
if (dcb->state == DCB_STATE_DISCONNECTED)
|
|
{
|
|
goto return_1;
|
|
}
|
|
|
|
if (dcb->protocol == NULL)
|
|
{
|
|
goto return_1;
|
|
}
|
|
protocol = (MySQLProtocol *)dcb->protocol;
|
|
CHK_PROTOCOL(protocol);
|
|
|
|
if (protocol->protocol_auth_state == MYSQL_IDLE)
|
|
{
|
|
dcb_drain_writeq(dcb);
|
|
goto return_1;
|
|
}
|
|
|
|
return_1:
|
|
#if defined(SS_DEBUG)
|
|
if (dcb->state == DCB_STATE_POLLING ||
|
|
dcb->state == DCB_STATE_NOPOLLING ||
|
|
dcb->state == DCB_STATE_ZOMBIE)
|
|
{
|
|
CHK_PROTOCOL(protocol);
|
|
}
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Bind the DCB to a network port or a UNIX Domain Socket.
|
|
* @param listen_dcb Listener DCB
|
|
* @param config_bind Bind address in either IP:PORT format for network sockets or PATH
|
|
* for UNIX Domain Sockets
|
|
* @return 1 on success, 0 on error
|
|
*/
|
|
int gw_MySQLListener(DCB *listen_dcb, char *config_bind)
|
|
{
|
|
if (dcb_listen(listen_dcb, config_bind, "MySQL") < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
listen_dcb->func.accept = gw_MySQLAccept;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Accept a new connection, using the DCB code for the basic work
|
|
*
|
|
* For as long as dcb_accept can return new client DCBs for new connections,
|
|
* continue to loop. The code will always give a failure return, since it
|
|
* continues to try to create new connections until a failure occurs.
|
|
*
|
|
* @param listener - The Listener DCB that picks up new connection requests
|
|
* @return 0 in success, 1 in failure
|
|
*
|
|
*/
|
|
int gw_MySQLAccept(DCB *listener)
|
|
{
|
|
DCB *client_dcb;
|
|
MySQLProtocol *protocol;
|
|
|
|
CHK_DCB(listener);
|
|
|
|
if (DCB_STATE_WAITING == listener->state)
|
|
{
|
|
gw_process_one_new_client(listener);
|
|
}
|
|
else
|
|
{
|
|
while ((client_dcb = dcb_accept(listener, &MyObject)) != NULL)
|
|
{
|
|
gw_process_one_new_client(client_dcb);
|
|
} /**< while client_dcb != NULL */
|
|
}
|
|
|
|
/* Must have broken out of while loop or received NULL client_dcb */
|
|
return 1;
|
|
}
|
|
|
|
static void gw_process_one_new_client(DCB *client_dcb)
|
|
{
|
|
MySQLProtocol *protocol;
|
|
|
|
CHK_DCB(client_dcb);
|
|
protocol = mysql_protocol_init(client_dcb, client_dcb->fd);
|
|
|
|
if (protocol == NULL)
|
|
{
|
|
/** delete client_dcb */
|
|
dcb_close(client_dcb);
|
|
MXS_ERROR("%lu [gw_MySQLAccept] Failed to create "
|
|
"protocol object for client connection.",
|
|
pthread_self());
|
|
return;
|
|
}
|
|
CHK_PROTOCOL(protocol);
|
|
client_dcb->protocol = protocol;
|
|
if (DCB_STATE_WAITING == client_dcb->state)
|
|
{
|
|
client_dcb->state = DCB_STATE_ALLOC;
|
|
}
|
|
else
|
|
{
|
|
atomic_add(&client_dcb->service->client_count, 1);
|
|
}
|
|
//send handshake to the client_dcb
|
|
MySQLSendHandshake(client_dcb);
|
|
|
|
// client protocol state change
|
|
protocol->protocol_auth_state = MYSQL_AUTH_SENT;
|
|
|
|
/**
|
|
* Set new descriptor to event set. At the same time,
|
|
* change state to DCB_STATE_POLLING so that
|
|
* thread which wakes up sees correct state.
|
|
*/
|
|
if (poll_add_dcb(client_dcb) == -1)
|
|
{
|
|
/* Send a custom error as MySQL command reply */
|
|
mysql_send_custom_error(client_dcb,
|
|
1,
|
|
0,
|
|
"MaxScale encountered system limit while "
|
|
"attempting to register on an epoll instance.");
|
|
|
|
/** close client_dcb */
|
|
dcb_close(client_dcb);
|
|
|
|
/** Previous state is recovered in poll_add_dcb. */
|
|
MXS_ERROR("%lu [gw_MySQLAccept] Failed to add dcb %p for "
|
|
"fd %d to epoll set.",
|
|
pthread_self(),
|
|
client_dcb,
|
|
client_dcb->fd);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
MXS_DEBUG("%lu [gw_MySQLAccept] Added dcb %p for fd "
|
|
"%d to epoll set.",
|
|
pthread_self(),
|
|
client_dcb,
|
|
client_dcb->fd);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int gw_error_client_event(DCB* dcb)
|
|
{
|
|
SESSION* session;
|
|
|
|
CHK_DCB(dcb);
|
|
|
|
session = dcb->session;
|
|
|
|
MXS_DEBUG("%lu [gw_error_client_event] Error event handling for DCB %p "
|
|
"in state %s, session %p.",
|
|
pthread_self(),
|
|
dcb,
|
|
STRDCBSTATE(dcb->state),
|
|
(session != NULL ? session : NULL));
|
|
|
|
if (session != NULL && session->state == SESSION_STATE_STOPPING)
|
|
{
|
|
goto retblock;
|
|
}
|
|
|
|
#if defined(SS_DEBUG)
|
|
MXS_DEBUG("Client error event handling.");
|
|
#endif
|
|
dcb_close(dcb);
|
|
|
|
retblock:
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
gw_client_close(DCB *dcb)
|
|
{
|
|
SESSION* session;
|
|
ROUTER_OBJECT* router;
|
|
void* router_instance;
|
|
#if defined(SS_DEBUG)
|
|
MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol;
|
|
|
|
if (dcb->state == DCB_STATE_POLLING ||
|
|
dcb->state == DCB_STATE_NOPOLLING ||
|
|
dcb->state == DCB_STATE_ZOMBIE)
|
|
{
|
|
if (!DCB_IS_CLONE(dcb))
|
|
{
|
|
CHK_PROTOCOL(protocol);
|
|
}
|
|
}
|
|
#endif
|
|
MXS_DEBUG("%lu [gw_client_close]", pthread_self());
|
|
mysql_protocol_done(dcb);
|
|
session = dcb->session;
|
|
/**
|
|
* session may be NULL if session_alloc failed.
|
|
* In that case, router session wasn't created.
|
|
*/
|
|
if (session != NULL && SESSION_STATE_DUMMY != session->state)
|
|
{
|
|
CHK_SESSION(session);
|
|
spinlock_acquire(&session->ses_lock);
|
|
|
|
if (session->state != SESSION_STATE_STOPPING)
|
|
{
|
|
session->state = SESSION_STATE_STOPPING;
|
|
}
|
|
router_instance = session->service->router_instance;
|
|
router = session->service->router;
|
|
/**
|
|
* If router session is being created concurrently router
|
|
* session might be NULL and it shouldn't be closed.
|
|
*/
|
|
if (session->router_session != NULL)
|
|
{
|
|
spinlock_release(&session->ses_lock);
|
|
/** Close router session and all its connections */
|
|
router->closeSession(router_instance, session->router_session);
|
|
}
|
|
else
|
|
{
|
|
spinlock_release(&session->ses_lock);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Handle a hangup event on the client side descriptor.
|
|
*
|
|
* We simply close the DCB, this will propogate the closure to any
|
|
* backend descriptors and perform the session cleanup.
|
|
*
|
|
* @param dcb The DCB of the connection
|
|
*/
|
|
static int gw_client_hangup_event(DCB *dcb)
|
|
{
|
|
SESSION* session;
|
|
|
|
CHK_DCB(dcb);
|
|
session = dcb->session;
|
|
|
|
if (session != NULL && session->state == SESSION_STATE_ROUTER_READY)
|
|
{
|
|
CHK_SESSION(session);
|
|
}
|
|
|
|
if (session != NULL && session->state == SESSION_STATE_STOPPING)
|
|
{
|
|
goto retblock;
|
|
}
|
|
|
|
dcb_close(dcb);
|
|
|
|
retblock:
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Detect if buffer includes partial mysql packet or multiple packets.
|
|
* Store partial packet to dcb_readqueue. Send complete packets one by one
|
|
* to router.
|
|
*
|
|
* It is assumed readbuf includes at least one complete packet.
|
|
* Return 1 in success. If the last packet is incomplete return success but
|
|
* leave incomplete packet to readbuf.
|
|
*
|
|
* @param session Session pointer
|
|
* @param p_readbuf Pointer to the address of GWBUF including the query
|
|
*
|
|
* @return 1 if succeed,
|
|
*/
|
|
static int route_by_statement(SESSION* session, GWBUF** p_readbuf)
|
|
{
|
|
int rc;
|
|
GWBUF* packetbuf;
|
|
#if defined(SS_DEBUG)
|
|
GWBUF* tmpbuf;
|
|
|
|
tmpbuf = *p_readbuf;
|
|
while (tmpbuf != NULL)
|
|
{
|
|
ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf));
|
|
tmpbuf=tmpbuf->next;
|
|
}
|
|
#endif
|
|
do
|
|
{
|
|
ss_dassert(GWBUF_IS_TYPE_MYSQL((*p_readbuf)));
|
|
|
|
/**
|
|
* Collect incoming bytes to a buffer until complete packet has
|
|
* arrived and then return the buffer.
|
|
*/
|
|
packetbuf = gw_MySQL_get_next_packet(p_readbuf);
|
|
|
|
if (packetbuf != NULL)
|
|
{
|
|
CHK_GWBUF(packetbuf);
|
|
ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf));
|
|
/**
|
|
* This means that buffer includes exactly one MySQL
|
|
* statement.
|
|
* backend func.write uses the information. MySQL backend
|
|
* protocol, for example, stores the command identifier
|
|
* to protocol structure. When some other thread reads
|
|
* the corresponding response the command tells how to
|
|
* handle response.
|
|
*
|
|
* Set it here instead of gw_read_client_event to make
|
|
* sure it is set to each (MySQL) packet.
|
|
*/
|
|
gwbuf_set_type(packetbuf, GWBUF_TYPE_SINGLE_STMT);
|
|
/** Route query */
|
|
rc = SESSION_ROUTE_QUERY(session, packetbuf);
|
|
}
|
|
else
|
|
{
|
|
rc = 1;
|
|
goto return_rc;
|
|
}
|
|
}
|
|
while (rc == 1 && *p_readbuf != NULL);
|
|
|
|
return_rc:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* if read queue existed appent read to it. if length of read buffer is less
|
|
* than 3 or less than mysql packet then return. else copy mysql packets to
|
|
* separate buffers from read buffer and continue. else if read queue didn't
|
|
* exist, length of read is less than 3 or less than mysql packet then
|
|
* create read queue and append to it and return. if length read is less than
|
|
* mysql packet length append to read queue append to it and return.
|
|
* else (complete packet was read) continue.
|
|
*
|
|
* @return True if we have a complete packet, otherwise false
|
|
*/
|
|
static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read)
|
|
{
|
|
if (dcb->dcb_readqueue)
|
|
{
|
|
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, *read_buffer);
|
|
nbytes_read = gwbuf_length(dcb->dcb_readqueue);
|
|
int plen = MYSQL_GET_PACKET_LEN((uint8_t *) GWBUF_DATA(dcb->dcb_readqueue));
|
|
|
|
if (nbytes_read < 3 || nbytes_read < plen + 4)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* There is at least one complete mysql packet in
|
|
* read_buffer.
|
|
*/
|
|
*read_buffer = dcb->dcb_readqueue;
|
|
dcb->dcb_readqueue = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint8_t* data = (uint8_t *) GWBUF_DATA(*read_buffer);
|
|
|
|
if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data) + 4)
|
|
{
|
|
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, *read_buffer);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|