MXS-862: Create common MySQL library

The MySQLCommon library contains functions used by both the protocol and
authenticator modules. The contents of the modutil.c file could also be
moved to this file if the functions in that file are only used by modules
and not the core.
This commit is contained in:
Markus Makela 2016-10-05 18:02:15 +03:00
parent d87f15b7f6
commit cb7c112764
14 changed files with 497 additions and 514 deletions

View File

@ -30,7 +30,7 @@ target_link_libraries(test_log maxscale-common)
target_link_libraries(test_logorder maxscale-common)
target_link_libraries(test_logthrottling maxscale-common)
target_link_libraries(test_modutil maxscale-common)
target_link_libraries(test_mysql_users MySQLClient maxscale-common)
target_link_libraries(test_mysql_users MySQLAuth MySQLCommon maxscale-common)
target_link_libraries(test_poll maxscale-common)
target_link_libraries(test_queuemanager maxscale-common)
target_link_libraries(test_server maxscale-common)

View File

@ -1,21 +1,20 @@
add_library(MySQLAuth SHARED mysql_auth.c)
target_link_libraries(MySQLAuth maxscale-common)
target_link_libraries(MySQLAuth maxscale-common MySQLCommon)
set_target_properties(MySQLAuth PROPERTIES VERSION "1.0.0")
install_module(MySQLAuth core)
add_library(MySQLBackendAuth SHARED mysql_backend_auth.c)
target_link_libraries(MySQLBackendAuth maxscale-common MySQLBackend)
target_link_libraries(MySQLBackendAuth maxscale-common MySQLCommon)
set_target_properties(MySQLBackendAuth PROPERTIES VERSION "1.0.0")
install_module(MySQLBackendAuth core)
add_library(GSSAPIAuth SHARED gssapi_auth.c gssapi_auth_common.c)
target_link_libraries(GSSAPIAuth maxscale-common gssapi_krb5)
target_link_libraries(GSSAPIAuth maxscale-common gssapi_krb5 MySQLCommon)
set_target_properties(GSSAPIAuth PROPERTIES VERSION "1.0.0")
install_module(GSSAPIAuth core)
add_library(GSSAPIBackendAuth SHARED gssapi_backend_auth.c gssapi_auth_common.c)
target_link_libraries(GSSAPIBackendAuth maxscale-common gssapi_krb5
MySQLBackend) # Needed for gw_send_backend_auth
target_link_libraries(GSSAPIBackendAuth maxscale-common gssapi_krb5 MySQLCommon)
set_target_properties(GSSAPIBackendAuth PROPERTIES VERSION "1.0.0")
install_module(GSSAPIBackendAuth core)

View File

@ -281,26 +281,10 @@ int gssapi_auth_authenticate(DCB *dcb)
MYSQL_session *ses = (MYSQL_session*)dcb->data;
if (validate_gssapi_token(ses->auth_token, ses->auth_token_len))
if (validate_gssapi_token(ses->auth_token, ses->auth_token_len) &&
mxs_mysql_send_ok(dcb, 4, 0, NULL))
{
/** Auth token is valid, send the OK packet
* @see https://dev.mysql.com/doc/internals/en/packet-OK_Packet.html */
uint8_t ok_packet[] =
{
0x07, 0x00, 0x00, 0x04, // Header
0x00, // OK byte
0x00, // Affected rows
0x00, // Last insert id
0x02, 0x00, // Status flags
0x00, 0x00 // Warnings
};
GWBUF *buffer = gwbuf_alloc_and_load(sizeof(ok_packet), ok_packet);
if (buffer && dcb->func.write(dcb, buffer))
{
rval = MXS_AUTH_SUCCEEDED;
}
rval = MXS_AUTH_SUCCEEDED;
}
}

View File

@ -192,6 +192,8 @@ mysql_auth_authenticate(DCB *dcb)
if (MXS_AUTH_SUCCEEDED == auth_ret)
{
dcb->user = MXS_STRDUP_A(client_data->user);
/** Send an OK packet to the client */
mxs_mysql_send_ok(dcb, ssl_required_by_dcb(dcb) ? 3 : 2, 0, NULL);
}
else if (dcb->service->log_auth_warnings)
{

View File

@ -380,4 +380,7 @@ bool gw_read_backend_handshake(DCB *dcb, GWBUF *buffer);
/** Send the server handshake response packet to the backend server */
mxs_auth_state_t gw_send_backend_auth(DCB *dcb);
/** Write an OK packet to a DCB */
int mxs_mysql_send_ok(DCB *dcb, int sequence, int affected_rows, const char* message);
#endif /** _MYSQL_PROTOCOL_H */

View File

@ -5,8 +5,7 @@ endif()
add_subdirectory(HTTPD)
add_subdirectory(maxscaled)
add_subdirectory(MySQLBackend)
add_subdirectory(MySQLClient)
add_subdirectory(MySQL)
add_subdirectory(telnetd)
if(BUILD_TESTS)

View File

@ -0,0 +1,7 @@
add_library(MySQLCommon SHARED mysql_common.c)
target_link_libraries(MySQLCommon maxscale-common)
set_target_properties(MySQLCommon PROPERTIES VERSION "2.0.0")
install_module(MySQLCommon core)
add_subdirectory(MySQLBackend)
add_subdirectory(MySQLClient)

View File

@ -0,0 +1,7 @@
add_library(MySQLBackend SHARED mysql_backend.c)
# TODO: Refactor MySQLBackend so that COM_CHANGE_USER processing is
# transparent to the protocol module. After this change, we don't need to
# link against MySQLAuth.
target_link_libraries(MySQLBackend maxscale-common MySQLCommon MySQLAuth)
set_target_properties(MySQLBackend PROPERTIES VERSION "2.0.0")
install_module(MySQLBackend core)

View File

@ -661,290 +661,6 @@ gw_reply_on_error(DCB *dcb, mxs_auth_state_t state)
gwbuf_free(errbuf);
}
/**
* @brief Computes the size of the response to the DB initial handshake
*
* When the connection is to be SSL, but an SSL connection has not yet been
* established, only a basic 36 byte response is sent, including the SSL
* capability flag.
*
* Otherwise, the packet size is computed, based on the minimum size and
* increased by the optional or variable elements.
*
* @param conn The MySQLProtocol structure for the connection
* @param user Name of the user seeking to connect
* @param passwd Password for the user seeking to connect
* @param dbname Name of the database to be made default, if any
* @return The length of the response packet
*/
static int
response_length(MySQLProtocol *conn, char *user, uint8_t *passwd, char *dbname, const char *auth_module)
{
long bytes;
if (conn->owner_dcb->server->server_ssl && conn->owner_dcb->ssl_state != SSL_ESTABLISHED)
{
return 36;
}
// Protocol MySQL HandshakeResponse for CLIENT_PROTOCOL_41
// 4 bytes capabilities + 4 bytes max packet size + 1 byte charset + 23 '\0' bytes
// 4 + 4 + 1 + 23 = 32
bytes = 32;
if (user)
{
bytes += strlen(user);
}
// the NULL
bytes++;
// next will be + 1 (scramble_len) + 20 (fixed_scramble) + 1 (user NULL term) + 1 (db NULL term)
if (passwd)
{
bytes += GW_MYSQL_SCRAMBLE_SIZE;
}
bytes++;
if (dbname && strlen(dbname))
{
bytes += strlen(dbname);
bytes++;
}
bytes += strlen(auth_module);
bytes++;
// the packet header
bytes += 4;
return bytes;
}
/**
* @brief Helper function to load hashed password
* @param conn DCB Protocol object
* @param payload Destination where hashed password is written
* @param passwd Client's double SHA1 password
* @return Address of the next byte after the end of the stored password
*/
static uint8_t *
load_hashed_password(uint8_t *scramble, uint8_t *payload, uint8_t *passwd)
{
uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE] = "";
uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE] = "";
uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE] = "";
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
// hash1 is the function input, SHA1(real_password)
memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE);
// hash2 is the SHA1(input data), where input_data = SHA1(real_password)
gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);
// new_sha is the SHA1(CONCAT(scramble, hash2)
gw_sha1_2_str(scramble, GW_MYSQL_SCRAMBLE_SIZE, hash2, GW_MYSQL_SCRAMBLE_SIZE, new_sha);
// compute the xor in client_scramble
gw_str_xor(client_scramble, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE);
// set the auth-length
*payload = GW_MYSQL_SCRAMBLE_SIZE;
payload++;
//copy the 20 bytes scramble data after packet_buffer + 36 + user + NULL + 1 (byte of auth-length)
memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE);
payload += GW_MYSQL_SCRAMBLE_SIZE;
return payload;
}
/**
* @brief Computes the capabilities bit mask for connecting to backend DB
*
* We start by taking the default bitmask and removing any bits not set in
* the bitmask contained in the connection structure. Then add SSL flag if
* the connection requires SSL (set from the MaxScale configuration). The
* compression flag may be set, although compression is NOT SUPPORTED. If a
* database name has been specified in the function call, the relevant flag
* is set.
*
* @param conn The MySQLProtocol structure for the connection
* @param db_specified Whether the connection request specified a database
* @param compress Whether compression is requested - NOT SUPPORTED
* @return Bit mask (32 bits)
* @note Capability bits are defined in mysql_client_server_protocol.h
*/
static uint32_t
create_capabilities(MySQLProtocol *conn, bool db_specified, bool compress)
{
uint32_t final_capabilities;
/** Copy client's flags to backend but with the known capabilities mask */
final_capabilities = (conn->client_capabilities & (uint32_t)GW_MYSQL_CAPABILITIES_CLIENT);
if (conn->owner_dcb->server->server_ssl)
{
final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_SSL;
/* Unclear whether we should include this */
/* Maybe it should depend on whether CA certificate is provided */
/* final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_SSL_VERIFY_SERVER_CERT; */
}
/* Compression is not currently supported */
if (compress)
{
final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
}
if (db_specified)
{
/* With database specified */
final_capabilities |= (int)GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
else
{
/* Without database specified */
final_capabilities &= ~(int)GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
final_capabilities |= (int)GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
return final_capabilities;
}
/**
* Write MySQL authentication packet to backend server
*
* @param dcb Backend DCB
* @return True on success, false on failure
*/
mxs_auth_state_t gw_send_backend_auth(DCB *dcb)
{
MYSQL_session local_session;
gw_get_shared_session_auth_info(dcb, &local_session);
uint8_t client_capabilities[4] = {0, 0, 0, 0};
uint8_t *curr_passwd = memcmp(local_session.client_sha1, null_client_sha1, MYSQL_SCRAMBLE_LEN) ?
local_session.client_sha1 : NULL;
/**
* If session is stopping or has failed return with error.
*/
if (dcb->session == NULL ||
(dcb->session->state != SESSION_STATE_READY &&
dcb->session->state != SESSION_STATE_ROUTER_READY) ||
(dcb->server->server_ssl &&
dcb->ssl_state != SSL_HANDSHAKE_FAILED))
{
return MXS_AUTH_STATE_FAILED;
}
MySQLProtocol *conn = (MySQLProtocol*)dcb->protocol;
uint32_t capabilities = create_capabilities(conn, (local_session.db && strlen(local_session.db)), false);
gw_mysql_set_byte4(client_capabilities, capabilities);
/**
* Use the default authentication plugin name. If the server is using a
* different authentication mechanism, it will send an AuthSwitchRequest
* packet.
*/
const char* auth_plugin_name = DEFAULT_MYSQL_AUTH_PLUGIN;
long bytes = response_length(conn, local_session.user, local_session.client_sha1,
local_session.db, auth_plugin_name);
// allocating the GWBUF
GWBUF *buffer = gwbuf_alloc(bytes);
uint8_t *payload = GWBUF_DATA(buffer);
// clearing data
memset(payload, '\0', bytes);
// put here the paylod size: bytes to write - 4 bytes packet header
gw_mysql_set_byte3(payload, (bytes - 4));
// set packet # = 1
payload[3] = (SSL_ESTABLISHED == dcb->ssl_state) ? '\x02' : '\x01';
payload += 4;
// set client capabilities
memcpy(payload, client_capabilities, 4);
// set now the max-packet size
payload += 4;
gw_mysql_set_byte4(payload, 16777216);
// set the charset
payload += 4;
*payload = conn->charset;
payload++;
// 23 bytes of 0
payload += 23;
if (dcb->server->server_ssl && dcb->ssl_state != SSL_ESTABLISHED)
{
if (dcb_write(dcb, buffer) && dcb_connect_SSL(dcb) >= 0)
{
return MXS_AUTH_STATE_CONNECTED;
}
return MXS_AUTH_STATE_FAILED;
}
// 4 + 4 + 4 + 1 + 23 = 36, this includes the 4 bytes packet header
memcpy(payload, local_session.user, strlen(local_session.user));
payload += strlen(local_session.user);
payload++;
if (curr_passwd != NULL)
{
payload = load_hashed_password(conn->scramble, payload, curr_passwd);
}
else
{
payload++;
}
// if the db is not NULL append it
if (local_session.db[0])
{
memcpy(payload, local_session.db, strlen(local_session.db));
payload += strlen(local_session.db);
payload++;
}
memcpy(payload, auth_plugin_name, strlen(auth_plugin_name));
return dcb_write(dcb, buffer) ? MXS_AUTH_STATE_RESPONSE_SENT : MXS_AUTH_STATE_FAILED;
}
/**
* Read the backend server MySQL handshake
*
* @param dcb Backend DCB
* @return true on success, false on failure
*/
bool gw_read_backend_handshake(DCB *dcb, GWBUF *buffer)
{
MySQLProtocol *proto = (MySQLProtocol *)dcb->protocol;
bool rval = false;
uint8_t *payload = GWBUF_DATA(buffer) + 4;
if (gw_decode_mysql_server_handshake(proto, payload) >= 0)
{
rval = true;
}
return rval;
}
/**
* @brief With authentication completed, read new data and write to backend
*
@ -1649,6 +1365,8 @@ static int backend_write_delayqueue(DCB *dcb, GWBUF *buffer)
/**
* This routine handles the COM_CHANGE_USER command
*
* TODO: Move this into the authenticators
*
* @param dcb The current backend DCB
* @param server The backend server pointer
* @param in_session The current session data (MYSQL_session)
@ -2028,105 +1746,6 @@ static bool sescmd_response_complete(DCB* dcb)
return succp;
}
/**
* gw_decode_mysql_server_handshake
*
* Decode mysql server handshake
*
* @param conn The MySQLProtocol structure
* @param payload The bytes just read from the net
* @return 0 on success, < 0 on failure
*
*/
static int
gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload)
{
uint8_t *server_version_end = NULL;
uint16_t mysql_server_capabilities_one = 0;
uint16_t mysql_server_capabilities_two = 0;
unsigned long tid = 0;
uint8_t scramble_data_1[GW_SCRAMBLE_LENGTH_323] = "";
uint8_t scramble_data_2[GW_MYSQL_SCRAMBLE_SIZE - GW_SCRAMBLE_LENGTH_323] = "";
uint8_t capab_ptr[4] = "";
int scramble_len = 0;
uint8_t mxs_scramble[GW_MYSQL_SCRAMBLE_SIZE] = "";
int protocol_version = 0;
protocol_version = payload[0];
if (protocol_version != GW_MYSQL_PROTOCOL_VERSION)
{
return -1;
}
payload++;
// Get server version (string)
server_version_end = (uint8_t *) gw_strend((char*) payload);
payload = server_version_end + 1;
// get ThreadID: 4 bytes
tid = gw_mysql_get_byte4(payload);
memcpy(&conn->tid, &tid, 4);
payload += 4;
// scramble_part 1
memcpy(scramble_data_1, payload, GW_SCRAMBLE_LENGTH_323);
payload += GW_SCRAMBLE_LENGTH_323;
// 1 filler
payload++;
mysql_server_capabilities_one = gw_mysql_get_byte2(payload);
//Get capabilities_part 1 (2 bytes) + 1 language + 2 server_status
payload += 5;
mysql_server_capabilities_two = gw_mysql_get_byte2(payload);
memcpy(capab_ptr, &mysql_server_capabilities_one, 2);
// get capabilities part 2 (2 bytes)
memcpy(&capab_ptr[2], &mysql_server_capabilities_two, 2);
// 2 bytes shift
payload += 2;
// get scramble len
if (payload[0] > 0)
{
scramble_len = payload[0] - 1;
ss_dassert(scramble_len > GW_SCRAMBLE_LENGTH_323);
ss_dassert(scramble_len <= GW_MYSQL_SCRAMBLE_SIZE);
if ((scramble_len < GW_SCRAMBLE_LENGTH_323) ||
scramble_len > GW_MYSQL_SCRAMBLE_SIZE)
{
/* log this */
return -2;
}
}
else
{
scramble_len = GW_MYSQL_SCRAMBLE_SIZE;
}
// skip 10 zero bytes
payload += 11;
// copy the second part of the scramble
memcpy(scramble_data_2, payload, scramble_len - GW_SCRAMBLE_LENGTH_323);
memcpy(mxs_scramble, scramble_data_1, GW_SCRAMBLE_LENGTH_323);
memcpy(mxs_scramble + GW_SCRAMBLE_LENGTH_323, scramble_data_2, scramble_len - GW_SCRAMBLE_LENGTH_323);
// full 20 bytes scramble is ready
memcpy(conn->scramble, mxs_scramble, GW_MYSQL_SCRAMBLE_SIZE);
return 0;
}
static void inline
close_socket(int sock)
{

View File

@ -0,0 +1,4 @@
add_library(MySQLClient SHARED mysql_client.c)
target_link_libraries(MySQLClient maxscale-common MySQLCommon)
set_target_properties(MySQLClient PROPERTIES VERSION "1.0.0")
install_module(MySQLClient core)

View File

@ -86,7 +86,6 @@ 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);
@ -162,92 +161,6 @@ 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
@ -625,18 +538,10 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
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);
session->state != SESSION_STATE_DUMMY);
protocol->protocol_auth_state = MXS_AUTH_STATE_COMPLETE;
/**
* Send an AUTH_OK packet to the client,
* packet sequence is # packet_number
*/
mysql_send_ok(dcb, packet_number, 0, NULL);
}
else
{

View File

@ -1078,3 +1078,465 @@ bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session)
spinlock_release(&dcb->session->ses_lock);
return rval;
}
/**
* @brief Send a MySQL protocol OK message to the dcb (client)
*
* @param dcb DCB where packet is written
* @param sequence Packet sequence number
* @param affected_rows Number of affected rows
* * @param message SQL message
* @return 1 on success, 0 on error
*
*/
int mxs_mysql_send_ok(DCB *dcb, int sequence, int affected_rows, const char* 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 insert_id = 0;
uint8_t mysql_server_status[2];
uint8_t mysql_warning_counter[2];
GWBUF *buf;
mysql_payload_size =
sizeof(field_count) +
sizeof(affected_rows) +
sizeof(insert_id) +
sizeof(mysql_server_status) +
sizeof(mysql_warning_counter);
if (message != NULL)
{
mysql_payload_size += strlen(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] = sequence;
// 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 (message != NULL)
{
memcpy(mysql_payload, message, strlen(message));
}
// writing data in the Client buffer queue
return dcb->func.write(dcb, buf);
}
/**
* @brief Computes the size of the response to the DB initial handshake
*
* When the connection is to be SSL, but an SSL connection has not yet been
* established, only a basic 36 byte response is sent, including the SSL
* capability flag.
*
* Otherwise, the packet size is computed, based on the minimum size and
* increased by the optional or variable elements.
*
* @param conn The MySQLProtocol structure for the connection
* @param user Name of the user seeking to connect
* @param passwd Password for the user seeking to connect
* @param dbname Name of the database to be made default, if any
* @return The length of the response packet
*/
static int
response_length(MySQLProtocol *conn, char *user, uint8_t *passwd, char *dbname, const char *auth_module)
{
long bytes;
if (conn->owner_dcb->server->server_ssl && conn->owner_dcb->ssl_state != SSL_ESTABLISHED)
{
return MYSQL_AUTH_PACKET_BASE_SIZE;
}
// Protocol MySQL HandshakeResponse for CLIENT_PROTOCOL_41
// 4 bytes capabilities + 4 bytes max packet size + 1 byte charset + 23 '\0' bytes
// 4 + 4 + 1 + 23 = 32
bytes = 32;
if (user)
{
bytes += strlen(user);
}
// the NULL
bytes++;
// next will be + 1 (scramble_len) + 20 (fixed_scramble) + 1 (user NULL term) + 1 (db NULL term)
if (passwd)
{
bytes += GW_MYSQL_SCRAMBLE_SIZE;
}
bytes++;
if (dbname && strlen(dbname))
{
bytes += strlen(dbname);
bytes++;
}
bytes += strlen(auth_module);
bytes++;
// the packet header
bytes += 4;
return bytes;
}
/**
* @brief Helper function to load hashed password
* @param conn DCB Protocol object
* @param payload Destination where hashed password is written
* @param passwd Client's double SHA1 password
* @return Address of the next byte after the end of the stored password
*/
static uint8_t *
load_hashed_password(uint8_t *scramble, uint8_t *payload, uint8_t *passwd)
{
uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE] = "";
uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE] = "";
uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE] = "";
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
// hash1 is the function input, SHA1(real_password)
memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE);
// hash2 is the SHA1(input data), where input_data = SHA1(real_password)
gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);
// new_sha is the SHA1(CONCAT(scramble, hash2)
gw_sha1_2_str(scramble, GW_MYSQL_SCRAMBLE_SIZE, hash2, GW_MYSQL_SCRAMBLE_SIZE, new_sha);
// compute the xor in client_scramble
gw_str_xor(client_scramble, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE);
// set the auth-length
*payload = GW_MYSQL_SCRAMBLE_SIZE;
payload++;
//copy the 20 bytes scramble data after packet_buffer + 36 + user + NULL + 1 (byte of auth-length)
memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE);
payload += GW_MYSQL_SCRAMBLE_SIZE;
return payload;
}
/**
* @brief Computes the capabilities bit mask for connecting to backend DB
*
* We start by taking the default bitmask and removing any bits not set in
* the bitmask contained in the connection structure. Then add SSL flag if
* the connection requires SSL (set from the MaxScale configuration). The
* compression flag may be set, although compression is NOT SUPPORTED. If a
* database name has been specified in the function call, the relevant flag
* is set.
*
* @param conn The MySQLProtocol structure for the connection
* @param db_specified Whether the connection request specified a database
* @param compress Whether compression is requested - NOT SUPPORTED
* @return Bit mask (32 bits)
* @note Capability bits are defined in mysql_client_server_protocol.h
*/
static uint32_t
create_capabilities(MySQLProtocol *conn, bool db_specified, bool compress)
{
uint32_t final_capabilities;
/** Copy client's flags to backend but with the known capabilities mask */
final_capabilities = (conn->client_capabilities & (uint32_t)GW_MYSQL_CAPABILITIES_CLIENT);
if (conn->owner_dcb->server->server_ssl)
{
final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_SSL;
/* Unclear whether we should include this */
/* Maybe it should depend on whether CA certificate is provided */
/* final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_SSL_VERIFY_SERVER_CERT; */
}
/* Compression is not currently supported */
if (compress)
{
final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
}
if (db_specified)
{
/* With database specified */
final_capabilities |= (int)GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
else
{
/* Without database specified */
final_capabilities &= ~(int)GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
final_capabilities |= (int)GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
return final_capabilities;
}
/**
* Write MySQL authentication packet to backend server
*
* @param dcb Backend DCB
* @return True on success, false on failure
*/
mxs_auth_state_t gw_send_backend_auth(DCB *dcb)
{
MYSQL_session local_session;
gw_get_shared_session_auth_info(dcb, &local_session);
uint8_t client_capabilities[4] = {0, 0, 0, 0};
uint8_t *curr_passwd = memcmp(local_session.client_sha1, null_client_sha1, MYSQL_SCRAMBLE_LEN) ?
local_session.client_sha1 : NULL;
/**
* If session is stopping or has failed return with error.
*/
if (dcb->session == NULL ||
(dcb->session->state != SESSION_STATE_READY &&
dcb->session->state != SESSION_STATE_ROUTER_READY) ||
(dcb->server->server_ssl &&
dcb->ssl_state != SSL_HANDSHAKE_FAILED))
{
return MXS_AUTH_STATE_FAILED;
}
MySQLProtocol *conn = (MySQLProtocol*)dcb->protocol;
uint32_t capabilities = create_capabilities(conn, (local_session.db && strlen(local_session.db)), false);
gw_mysql_set_byte4(client_capabilities, capabilities);
/**
* Use the default authentication plugin name. If the server is using a
* different authentication mechanism, it will send an AuthSwitchRequest
* packet.
*/
const char* auth_plugin_name = DEFAULT_MYSQL_AUTH_PLUGIN;
long bytes = response_length(conn, local_session.user, local_session.client_sha1,
local_session.db, auth_plugin_name);
// allocating the GWBUF
GWBUF *buffer = gwbuf_alloc(bytes);
uint8_t *payload = GWBUF_DATA(buffer);
// clearing data
memset(payload, '\0', bytes);
// put here the paylod size: bytes to write - 4 bytes packet header
gw_mysql_set_byte3(payload, (bytes - 4));
// set packet # = 1
payload[3] = (SSL_ESTABLISHED == dcb->ssl_state) ? '\x02' : '\x01';
payload += 4;
// set client capabilities
memcpy(payload, client_capabilities, 4);
// set now the max-packet size
payload += 4;
gw_mysql_set_byte4(payload, 16777216);
// set the charset
payload += 4;
*payload = conn->charset;
payload++;
// 23 bytes of 0
payload += 23;
if (dcb->server->server_ssl && dcb->ssl_state != SSL_ESTABLISHED)
{
if (dcb_write(dcb, buffer) && dcb_connect_SSL(dcb) >= 0)
{
return MXS_AUTH_STATE_CONNECTED;
}
return MXS_AUTH_STATE_FAILED;
}
// 4 + 4 + 4 + 1 + 23 = 36, this includes the 4 bytes packet header
memcpy(payload, local_session.user, strlen(local_session.user));
payload += strlen(local_session.user);
payload++;
if (curr_passwd != NULL)
{
payload = load_hashed_password(conn->scramble, payload, curr_passwd);
}
else
{
payload++;
}
// if the db is not NULL append it
if (local_session.db[0])
{
memcpy(payload, local_session.db, strlen(local_session.db));
payload += strlen(local_session.db);
payload++;
}
memcpy(payload, auth_plugin_name, strlen(auth_plugin_name));
return dcb_write(dcb, buffer) ? MXS_AUTH_STATE_RESPONSE_SENT : MXS_AUTH_STATE_FAILED;
}
/**
* Decode mysql server handshake
*
* @param conn The MySQLProtocol structure
* @param payload The bytes just read from the net
* @return 0 on success, < 0 on failure
*
*/
static int
gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload)
{
uint8_t *server_version_end = NULL;
uint16_t mysql_server_capabilities_one = 0;
uint16_t mysql_server_capabilities_two = 0;
unsigned long tid = 0;
uint8_t scramble_data_1[GW_SCRAMBLE_LENGTH_323] = "";
uint8_t scramble_data_2[GW_MYSQL_SCRAMBLE_SIZE - GW_SCRAMBLE_LENGTH_323] = "";
uint8_t capab_ptr[4] = "";
int scramble_len = 0;
uint8_t mxs_scramble[GW_MYSQL_SCRAMBLE_SIZE] = "";
int protocol_version = 0;
protocol_version = payload[0];
if (protocol_version != GW_MYSQL_PROTOCOL_VERSION)
{
return -1;
}
payload++;
// Get server version (string)
server_version_end = (uint8_t *) gw_strend((char*) payload);
payload = server_version_end + 1;
// get ThreadID: 4 bytes
tid = gw_mysql_get_byte4(payload);
memcpy(&conn->tid, &tid, 4);
payload += 4;
// scramble_part 1
memcpy(scramble_data_1, payload, GW_SCRAMBLE_LENGTH_323);
payload += GW_SCRAMBLE_LENGTH_323;
// 1 filler
payload++;
mysql_server_capabilities_one = gw_mysql_get_byte2(payload);
//Get capabilities_part 1 (2 bytes) + 1 language + 2 server_status
payload += 5;
mysql_server_capabilities_two = gw_mysql_get_byte2(payload);
memcpy(capab_ptr, &mysql_server_capabilities_one, 2);
// get capabilities part 2 (2 bytes)
memcpy(&capab_ptr[2], &mysql_server_capabilities_two, 2);
// 2 bytes shift
payload += 2;
// get scramble len
if (payload[0] > 0)
{
scramble_len = payload[0] - 1;
ss_dassert(scramble_len > GW_SCRAMBLE_LENGTH_323);
ss_dassert(scramble_len <= GW_MYSQL_SCRAMBLE_SIZE);
if ((scramble_len < GW_SCRAMBLE_LENGTH_323) ||
scramble_len > GW_MYSQL_SCRAMBLE_SIZE)
{
/* log this */
return -2;
}
}
else
{
scramble_len = GW_MYSQL_SCRAMBLE_SIZE;
}
// skip 10 zero bytes
payload += 11;
// copy the second part of the scramble
memcpy(scramble_data_2, payload, scramble_len - GW_SCRAMBLE_LENGTH_323);
memcpy(mxs_scramble, scramble_data_1, GW_SCRAMBLE_LENGTH_323);
memcpy(mxs_scramble + GW_SCRAMBLE_LENGTH_323, scramble_data_2, scramble_len - GW_SCRAMBLE_LENGTH_323);
// full 20 bytes scramble is ready
memcpy(conn->scramble, mxs_scramble, GW_MYSQL_SCRAMBLE_SIZE);
return 0;
}
/**
* Read the backend server MySQL handshake
*
* @param dcb Backend DCB
* @return true on success, false on failure
*/
bool gw_read_backend_handshake(DCB *dcb, GWBUF *buffer)
{
MySQLProtocol *proto = (MySQLProtocol *)dcb->protocol;
bool rval = false;
uint8_t *payload = GWBUF_DATA(buffer) + 4;
if (gw_decode_mysql_server_handshake(proto, payload) >= 0)
{
rval = true;
}
return rval;
}

View File

@ -1,4 +0,0 @@
add_library(MySQLBackend SHARED mysql_backend.c ../mysql_common.c)
target_link_libraries(MySQLBackend maxscale-common MySQLAuth)
set_target_properties(MySQLBackend PROPERTIES VERSION "2.0.0")
install_module(MySQLBackend core)

View File

@ -1,4 +0,0 @@
add_library(MySQLClient SHARED mysql_client.c ../mysql_common.c)
target_link_libraries(MySQLClient maxscale-common MySQLAuth)
set_target_properties(MySQLClient PROPERTIES VERSION "1.0.0")
install_module(MySQLClient core)