MXS-862: Move backend authentication from MySQLBackend to MySQLBackendAuth
The authentication for backend connections is now done in the MySQLBackendAuth module. This is also the default authentication module for backend connections created by MySQLBackend.
This commit is contained in:
parent
35d9b35609
commit
6d057f8152
@ -397,6 +397,11 @@ dcb_free_all_memory(DCB *dcb)
|
||||
dcb->authfunc.free(dcb);
|
||||
dcb->data = NULL;
|
||||
}
|
||||
if (dcb->backend_data && dcb->authfunc.free && dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER)
|
||||
{
|
||||
dcb->authfunc.free(dcb);
|
||||
dcb->backend_data = NULL;
|
||||
}
|
||||
if (dcb->protoname)
|
||||
{
|
||||
MXS_FREE(dcb->protoname);
|
||||
|
@ -253,6 +253,7 @@ typedef struct dcb
|
||||
time_t persistentstart; /**< Time when DCB placed in persistent pool */
|
||||
struct service *service; /**< The related service */
|
||||
void *data; /**< Specific client data */
|
||||
void *backend_data; /**< Specific backend data */
|
||||
DCBMM memdata; /**< The data related to DCB memory management */
|
||||
SPINLOCK cb_lock; /**< The lock for the callbacks linked list */
|
||||
DCB_CALLBACK *callbacks; /**< The list of callbacks for the DCB */
|
||||
@ -282,7 +283,8 @@ typedef struct dcb
|
||||
.authlock = SPINLOCK_INIT, .stats = {0}, .memdata = DCBMM_INIT, \
|
||||
.cb_lock = SPINLOCK_INIT, .pollinlock = SPINLOCK_INIT, \
|
||||
.fd = DCBFD_CLOSED, .stats = DCBSTATS_INIT, .ssl_state = SSL_HANDSHAKE_UNKNOWN, \
|
||||
.state = DCB_STATE_ALLOC, .polloutlock = SPINLOCK_INIT, .dcb_chk_tail = CHK_NUM_DCB}
|
||||
.state = DCB_STATE_ALLOC, .polloutlock = SPINLOCK_INIT, .dcb_chk_tail = CHK_NUM_DCB, \
|
||||
.backend_data = NULL}
|
||||
|
||||
/**
|
||||
* The DCB usage filer used for returning DCB's in use for a certain reason
|
||||
|
@ -67,8 +67,8 @@ typedef struct gw_authenticator
|
||||
/** Return values for extract and authenticate entry points */
|
||||
#define MXS_AUTH_SUCCEEDED 0 /**< Authentication was successful */
|
||||
#define MXS_AUTH_FAILED 1 /**< Authentication failed */
|
||||
#define MXS_AUTH_FAILED_DB 2
|
||||
#define MXS_AUTH_FAILED_SSL 3
|
||||
#define MXS_AUTH_FAILED_DB 2 /**< Authentication failed, database not found */
|
||||
#define MXS_AUTH_FAILED_SSL 3 /**< SSL authentication failed */
|
||||
#define MXS_AUTH_INCOMPLETE 4 /**< Authentication is not yet complete */
|
||||
#define MXS_AUTH_SSL_INCOMPLETE 5 /**< SSL connection is not yet complete */
|
||||
#define MXS_AUTH_NO_SESSION 6
|
||||
|
@ -3,6 +3,11 @@ target_link_libraries(MySQLAuth maxscale-common)
|
||||
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)
|
||||
set_target_properties(MySQLBackendAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(MySQLBackendAuth core)
|
||||
|
||||
add_library(NullAuthAllow SHARED null_auth_allow.c)
|
||||
target_link_libraries(NullAuthAllow maxscale-common)
|
||||
set_target_properties(NullAuthAllow PROPERTIES VERSION "1.0.0")
|
||||
|
275
server/modules/authenticator/mysql_backend_auth.c
Normal file
275
server/modules/authenticator/mysql_backend_auth.c
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* 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_backend_auth.c - MySQL backend authenticator
|
||||
*
|
||||
* Backend authentication module for the MySQL protocol. Implements the
|
||||
* client side of the 'mysql_native_password' authentication plugin.
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
* Date Who Description
|
||||
* 27/09/2016 Markus Makela Initial version
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <gw_authenticator.h>
|
||||
#include <mysql_client_server_protocol.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <utils.h>
|
||||
|
||||
/** Authentication states */
|
||||
enum mba_state
|
||||
{
|
||||
MBA_NEED_HANDSHAKE, /**< Waiting for server's handshake packet */
|
||||
MBA_SEND_RESPONSE, /**< A response to the server's handshake has been sent */
|
||||
MBA_NEED_OK, /**< Waiting for server's OK packet */
|
||||
MBA_AUTH_OK, /**< Authentication completed successfully */
|
||||
MBA_AUTH_FAILED /**< Authentication failed */
|
||||
};
|
||||
|
||||
/** Structure representing the authentication state */
|
||||
typedef struct mysql_backend_auth
|
||||
{
|
||||
enum mba_state state; /**< Authentication state */
|
||||
} mysql_backend_auth_t;
|
||||
|
||||
/**
|
||||
* @brief Allocate a new mysql_backend_auth object
|
||||
* @return Allocated object or NULL if memory allocation failed
|
||||
*/
|
||||
mysql_backend_auth_t* mba_alloc()
|
||||
{
|
||||
mysql_backend_auth_t* mba = MXS_MALLOC(sizeof(*mba));
|
||||
|
||||
if (mba)
|
||||
{
|
||||
mba->state = MBA_NEED_HANDSHAKE;
|
||||
}
|
||||
|
||||
return mba;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive the MySQL authentication packet from backend, packet # is 2
|
||||
*
|
||||
* @param protocol The MySQL protocol structure
|
||||
* @return False in case of failure, true if authentication was successful.
|
||||
*/
|
||||
static bool gw_read_auth_response(DCB *dcb, GWBUF *buffer)
|
||||
{
|
||||
bool rval = false;
|
||||
uint8_t cmd;
|
||||
|
||||
if (gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, 1, &cmd) && cmd == MYSQL_REPLY_OK)
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extract backend response
|
||||
*
|
||||
* @param dcb Request handler DCB connected to the client
|
||||
* @param buffer Buffer containing data from client
|
||||
* @return Authentication status
|
||||
* @see gw_quthenticator.h
|
||||
* @see https://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||
*/
|
||||
static int
|
||||
auth_backend_extract(DCB *dcb, GWBUF *buf)
|
||||
{
|
||||
int rval = MXS_AUTH_FAILED;
|
||||
|
||||
if (dcb->backend_data || (dcb->backend_data = mba_alloc()))
|
||||
{
|
||||
mysql_backend_auth_t *mba = (mysql_backend_auth_t*)dcb->backend_data;
|
||||
|
||||
switch (mba->state)
|
||||
{
|
||||
case MBA_NEED_HANDSHAKE:
|
||||
if (gw_read_backend_handshake(dcb, buf))
|
||||
{
|
||||
rval = MXS_AUTH_INCOMPLETE;
|
||||
mba->state = MBA_SEND_RESPONSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
mba->state = MBA_AUTH_FAILED;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBA_NEED_OK:
|
||||
if (gw_read_auth_response(dcb, buf))
|
||||
{
|
||||
rval = MXS_AUTH_SUCCEEDED;
|
||||
mba->state = MBA_AUTH_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
mba->state = MBA_AUTH_FAILED;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
MXS_ERROR("Unexpected call to MySQLBackendAuth::extract");
|
||||
ss_dassert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Authenticates as a MySQL user
|
||||
*
|
||||
* @param dcb Backend DCB
|
||||
* @return Authentication status
|
||||
* @see gw_authenticator.h
|
||||
*/
|
||||
static int
|
||||
auth_backend_authenticate(DCB *dcb)
|
||||
{
|
||||
int rval = MXS_AUTH_FAILED;
|
||||
mysql_backend_auth_t *mba = (mysql_backend_auth_t*)dcb->backend_data;
|
||||
|
||||
if (mba->state == MBA_SEND_RESPONSE)
|
||||
{
|
||||
/** First message read, decode password and send the auth credentials to backend */
|
||||
switch (gw_send_backend_auth(dcb))
|
||||
{
|
||||
case MXS_AUTH_STATE_CONNECTED:
|
||||
rval = MXS_AUTH_SSL_INCOMPLETE;
|
||||
break;
|
||||
|
||||
case MXS_AUTH_STATE_RESPONSE_SENT:
|
||||
mba->state = MBA_NEED_OK;
|
||||
rval = MXS_AUTH_INCOMPLETE;
|
||||
break;
|
||||
|
||||
default:
|
||||
/** Authentication failed */
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (mba->state == MBA_AUTH_OK)
|
||||
{
|
||||
/** Authentication completed successfully */
|
||||
rval = MXS_AUTH_SUCCEEDED;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determine whether the client is SSL capable
|
||||
*
|
||||
* The authentication request from the client will indicate whether the client
|
||||
* is expecting to make an SSL connection. The information has been extracted
|
||||
* in the previous functions.
|
||||
*
|
||||
* @param dcb Request handler DCB connected to the client
|
||||
* @return Boolean indicating whether client is SSL capable
|
||||
*/
|
||||
static bool
|
||||
auth_backend_ssl(DCB *dcb)
|
||||
{
|
||||
return dcb->server->server_ssl != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dummy function for the free entry point
|
||||
*/
|
||||
static void
|
||||
auth_backend_free(DCB *dcb)
|
||||
{
|
||||
MXS_FREE(dcb->backend_data);
|
||||
dcb->backend_data = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dummy function for the loadusers entry point
|
||||
*/
|
||||
static int auth_backend_load_users(SERV_LISTENER *port)
|
||||
{
|
||||
return MXS_AUTH_LOADUSERS_OK;
|
||||
}
|
||||
|
||||
/* @see function load_module in load_utils.c for explanation of the following
|
||||
* lint directives.
|
||||
*/
|
||||
/*lint -e14 */
|
||||
MODULE_INFO info =
|
||||
{
|
||||
MODULE_API_AUTHENTICATOR,
|
||||
MODULE_GA,
|
||||
GWAUTHENTICATOR_VERSION,
|
||||
"The MySQL MaxScale to backend server authenticator"
|
||||
};
|
||||
/*lint +e14 */
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
|
||||
/*
|
||||
* The "module object" for mysql client authenticator module.
|
||||
*/
|
||||
static GWAUTHENTICATOR MyObject =
|
||||
{
|
||||
auth_backend_extract, /* Extract data into structure */
|
||||
auth_backend_ssl, /* Check if client supports SSL */
|
||||
auth_backend_authenticate, /* Authenticate user credentials */
|
||||
auth_backend_free, /* Free the client data held in DCB */
|
||||
auth_backend_load_users, /* Load users from backend databases */
|
||||
DEFAULT_MYSQL_AUTH_PLUGIN
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
GWAUTHENTICATOR* GetModuleObject()
|
||||
{
|
||||
return &MyObject;
|
||||
}
|
||||
/*lint +e14 */
|
@ -294,6 +294,8 @@ typedef struct
|
||||
#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==MYSQL_COM_CHANGE_USER)
|
||||
#define MYSQL_GET_NATTR(payload) ((int)payload[4])
|
||||
|
||||
/* The following can be compared using memcmp to detect a null password */
|
||||
extern uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN];
|
||||
|
||||
MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd);
|
||||
void mysql_protocol_done (DCB* dcb);
|
||||
@ -365,5 +367,12 @@ void init_response_status (
|
||||
int* npackets,
|
||||
ssize_t* nbytes);
|
||||
bool read_complete_packet(DCB *dcb, GWBUF **readbuf);
|
||||
bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session);
|
||||
|
||||
/** Read the backend server's handshake */
|
||||
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);
|
||||
|
||||
#endif /** _MYSQL_PROTOCOL_H */
|
||||
|
@ -21,9 +21,6 @@
|
||||
#include <gw.h>
|
||||
#include <mysqld_error.h>
|
||||
|
||||
/* The following can be compared using memcmp to detect a null password */
|
||||
uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN] = "";
|
||||
|
||||
/*
|
||||
* MySQL Protocol module for handling the protocol between the gateway
|
||||
* and the backend MySQL database.
|
||||
@ -87,13 +84,7 @@ extern char* create_auth_failed_msg(GWBUF* readbuf, char* hostaddr, uint8_t* sha
|
||||
static bool sescmd_response_complete(DCB* dcb);
|
||||
static void gw_reply_on_error(DCB *dcb, mxs_auth_state_t state);
|
||||
static int gw_read_and_write(DCB *dcb);
|
||||
static mxs_auth_state_t gw_read_backend_handshake(MySQLProtocol *conn, GWBUF *buffer);
|
||||
static int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload);
|
||||
static mxs_auth_state_t gw_read_auth_response(DCB *dcb, GWBUF *buffer);
|
||||
static mxs_auth_state_t gw_send_auth(MYSQL_session *ses, MySQLProtocol *conn);
|
||||
static uint32_t create_capabilities(MySQLProtocol *conn, bool db_specified, bool compress);
|
||||
static int response_length(MySQLProtocol *conn, char *user, uint8_t *passwd, char *dbname);
|
||||
static uint8_t *load_hashed_password(MySQLProtocol *conn, uint8_t *payload, uint8_t *passwd);
|
||||
static int gw_do_connect_to_backend(char *host, int port, int *fd);
|
||||
static void inline close_socket(int socket);
|
||||
static GWBUF *gw_create_change_user_packet(MYSQL_session* mses,
|
||||
@ -106,7 +97,6 @@ static int gw_send_change_user_to_backend(char *dbname,
|
||||
#if defined(NOT_USED)
|
||||
static int gw_session(DCB *backend_dcb, void *data);
|
||||
#endif
|
||||
static bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session);
|
||||
|
||||
static GWPROTOCOL MyObject =
|
||||
{
|
||||
@ -169,7 +159,7 @@ GWPROTOCOL* GetModuleObject()
|
||||
*/
|
||||
static char *gw_backend_default_auth()
|
||||
{
|
||||
return "NullAuthAllow";
|
||||
return "MySQLBackendAuth";
|
||||
}
|
||||
/*lint +e14 */
|
||||
|
||||
@ -470,29 +460,41 @@ void log_error_response(DCB *dcb, GWBUF *buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle the server's handshake packet
|
||||
* @brief Handle the server's response packet
|
||||
*
|
||||
* This function reads the server's handshake packet and does the first step of
|
||||
* This function reads the server's response packet and does the final step of
|
||||
* the authentication.
|
||||
*
|
||||
* @param dcb Backend DCB
|
||||
* @param buffer Buffer containing the server's complete handshake
|
||||
* @return MXS_AUTH_STATE_HANDSHAKE_FAILED on failure.
|
||||
*/
|
||||
mxs_auth_state_t handle_server_handshake(DCB *dcb, GWBUF *buffer)
|
||||
mxs_auth_state_t handle_server_response(DCB *dcb, GWBUF *buffer)
|
||||
{
|
||||
MySQLProtocol *proto = (MySQLProtocol *)dcb->protocol;
|
||||
mxs_auth_state_t rval = gw_read_backend_handshake(proto, buffer);
|
||||
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
||||
mxs_auth_state_t rval = proto->protocol_auth_state == MXS_AUTH_STATE_CONNECTED ?
|
||||
MXS_AUTH_STATE_HANDSHAKE_FAILED : MXS_AUTH_STATE_FAILED;
|
||||
|
||||
if (rval != MXS_AUTH_STATE_HANDSHAKE_FAILED)
|
||||
int rc = dcb->authfunc.extract(dcb, buffer);
|
||||
|
||||
if (rc == MXS_AUTH_SUCCEEDED || rc == MXS_AUTH_INCOMPLETE)
|
||||
{
|
||||
MYSQL_session local_session;
|
||||
gw_get_shared_session_auth_info(dcb, &local_session);
|
||||
switch (dcb->authfunc.authenticate(dcb))
|
||||
{
|
||||
case MXS_AUTH_INCOMPLETE:
|
||||
case MXS_AUTH_SSL_INCOMPLETE:
|
||||
rval = MXS_AUTH_STATE_RESPONSE_SENT;
|
||||
break;
|
||||
|
||||
/** First message read, decode password and send the auth credentials to backend */
|
||||
rval = gw_send_auth(&local_session, proto);
|
||||
case MXS_AUTH_SUCCEEDED:
|
||||
rval = MXS_AUTH_STATE_COMPLETE;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gwbuf_free(buffer);
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -578,45 +580,29 @@ gw_read_backend_event(DCB *dcb)
|
||||
log_error_response(dcb, readbuf);
|
||||
}
|
||||
|
||||
if (proto->protocol_auth_state == MXS_AUTH_STATE_CONNECTED)
|
||||
if (proto->protocol_auth_state == MXS_AUTH_STATE_CONNECTED ||
|
||||
proto->protocol_auth_state == MXS_AUTH_STATE_RESPONSE_SENT)
|
||||
{
|
||||
/** TODO: Convert this to a call to dcb->authfunc.authenticate */
|
||||
/** Read the first message from the server */
|
||||
proto->protocol_auth_state = handle_server_handshake(dcb, readbuf);
|
||||
readbuf = NULL;
|
||||
proto->protocol_auth_state = handle_server_response(dcb, readbuf);
|
||||
}
|
||||
else
|
||||
|
||||
if (proto->protocol_auth_state == MXS_AUTH_STATE_COMPLETE)
|
||||
{
|
||||
/**
|
||||
* Authentication process is ongoing. The default authentication
|
||||
* plugin, mysql_native_password, sends two messages: the initial
|
||||
* handshake and then either an OK or an ERR packet which signals
|
||||
* whether authentication was successful
|
||||
*/
|
||||
if (proto->protocol_auth_state == MXS_AUTH_STATE_RESPONSE_SENT)
|
||||
{
|
||||
/** TODO: Convert this to a call to dcb->authfunc.authenticate */
|
||||
proto->protocol_auth_state = gw_read_auth_response(dcb, readbuf);
|
||||
}
|
||||
/** Authentication completed successfully */
|
||||
spinlock_acquire(&dcb->authlock);
|
||||
GWBUF *localq = dcb->delayq;
|
||||
dcb->delayq = NULL;
|
||||
spinlock_release(&dcb->authlock);
|
||||
|
||||
if (proto->protocol_auth_state == MXS_AUTH_STATE_COMPLETE)
|
||||
if (localq)
|
||||
{
|
||||
/** Authentication completed successfully */
|
||||
spinlock_acquire(&dcb->authlock);
|
||||
GWBUF *localq = dcb->delayq;
|
||||
dcb->delayq = NULL;
|
||||
spinlock_release(&dcb->authlock);
|
||||
|
||||
if (localq)
|
||||
{
|
||||
/** Send the queued commands to the backend */
|
||||
rc = backend_write_delayqueue(dcb, localq);
|
||||
}
|
||||
/** Send the queued commands to the backend */
|
||||
rc = backend_write_delayqueue(dcb, localq);
|
||||
}
|
||||
}
|
||||
|
||||
if (proto->protocol_auth_state == MXS_AUTH_STATE_FAILED ||
|
||||
proto->protocol_auth_state == MXS_AUTH_STATE_HANDSHAKE_FAILED)
|
||||
else if (proto->protocol_auth_state == MXS_AUTH_STATE_FAILED ||
|
||||
proto->protocol_auth_state == MXS_AUTH_STATE_HANDSHAKE_FAILED)
|
||||
{
|
||||
/** Authentication failed */
|
||||
gw_reply_on_error(dcb, proto->protocol_auth_state);
|
||||
@ -627,168 +613,6 @@ gw_read_backend_event(DCB *dcb)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the backend server MySQL handshake
|
||||
*
|
||||
* TODO: Move this function inside a module
|
||||
*
|
||||
* @param conn MySQL protocol structure
|
||||
* @return 0 on success, 1 on failure
|
||||
*/
|
||||
static mxs_auth_state_t
|
||||
gw_read_backend_handshake(MySQLProtocol *conn, GWBUF *buffer)
|
||||
{
|
||||
mxs_auth_state_t rval = MXS_AUTH_STATE_HANDSHAKE_FAILED;
|
||||
uint8_t *payload = GWBUF_DATA(buffer) + 4;
|
||||
|
||||
if (gw_decode_mysql_server_handshake(conn, payload) >= 0)
|
||||
{
|
||||
rval = MXS_AUTH_STATE_MESSAGE_READ;
|
||||
}
|
||||
|
||||
gwbuf_free(buffer);
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write MySQL authentication packet to backend server
|
||||
*
|
||||
* @param conn MySQL protocol structure
|
||||
* @param dbname The selected database
|
||||
* @param user The selected user
|
||||
* @param passwd The SHA1(real_password): Note real_password is unknown
|
||||
* @return MySQL authorisation state after operation
|
||||
*/
|
||||
static mxs_auth_state_t
|
||||
gw_send_auth(MYSQL_session *ses, MySQLProtocol *conn)
|
||||
{
|
||||
uint8_t *payload;
|
||||
long bytes;
|
||||
uint32_t capabilities;
|
||||
uint8_t client_capabilities[4] = {0, 0, 0, 0};
|
||||
GWBUF *buffer;
|
||||
uint8_t *curr_passwd = memcmp(ses->client_sha1, null_client_sha1, MYSQL_SCRAMBLE_LEN) ?
|
||||
ses->client_sha1 : NULL;
|
||||
|
||||
/**
|
||||
* If session is stopping or has failed return with error.
|
||||
*/
|
||||
if (conn->owner_dcb->session == NULL ||
|
||||
(conn->owner_dcb->session->state != SESSION_STATE_READY &&
|
||||
conn->owner_dcb->session->state != SESSION_STATE_ROUTER_READY) ||
|
||||
(conn->owner_dcb->server->server_ssl &&
|
||||
conn->owner_dcb->ssl_state != SSL_HANDSHAKE_FAILED))
|
||||
{
|
||||
return MXS_AUTH_STATE_FAILED;
|
||||
}
|
||||
|
||||
capabilities = create_capabilities(conn, (ses->db && strlen(ses->db)), false);
|
||||
gw_mysql_set_byte4(client_capabilities, capabilities);
|
||||
|
||||
bytes = response_length(conn, ses->user, ses->client_sha1, ses->db);
|
||||
|
||||
// allocating the GWBUF
|
||||
buffer = gwbuf_alloc(bytes);
|
||||
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 == conn->owner_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 (conn->owner_dcb->server->server_ssl && conn->owner_dcb->ssl_state != SSL_ESTABLISHED)
|
||||
{
|
||||
if (dcb_write(conn->owner_dcb, buffer) && dcb_connect_SSL(conn->owner_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, ses->user, strlen(ses->user));
|
||||
payload += strlen(ses->user);
|
||||
payload++;
|
||||
|
||||
if (curr_passwd != NULL)
|
||||
{
|
||||
payload = load_hashed_password(conn, payload, curr_passwd);
|
||||
}
|
||||
else
|
||||
{
|
||||
payload++;
|
||||
}
|
||||
|
||||
// if the db is not NULL append it
|
||||
if (ses->db && strlen(ses->db))
|
||||
{
|
||||
memcpy(payload, ses->db, strlen(ses->db));
|
||||
payload += strlen(ses->db);
|
||||
payload++;
|
||||
}
|
||||
|
||||
const char* auth_plugin_name = conn->owner_dcb->authfunc.plugin_name ?
|
||||
conn->owner_dcb->authfunc.plugin_name : DEFAULT_MYSQL_AUTH_PLUGIN;
|
||||
|
||||
memcpy(payload, auth_plugin_name, strlen(auth_plugin_name));
|
||||
|
||||
return dcb_write(conn->owner_dcb, buffer) ? MXS_AUTH_STATE_RESPONSE_SENT : MXS_AUTH_STATE_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy shared session authentication info
|
||||
*
|
||||
* @param dcb A backend DCB
|
||||
* @param session Destination where authentication data is copied
|
||||
* @return bool true = success, false = fail
|
||||
*/
|
||||
static bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session)
|
||||
{
|
||||
bool rval = true;
|
||||
CHK_DCB(dcb);
|
||||
CHK_SESSION(dcb->session);
|
||||
|
||||
spinlock_acquire(&dcb->session->ses_lock);
|
||||
|
||||
if (dcb->session->state != SESSION_STATE_ALLOC &&
|
||||
dcb->session->state != SESSION_STATE_DUMMY)
|
||||
{
|
||||
memcpy(session, dcb->session->client_dcb->data, sizeof(MYSQL_session));
|
||||
}
|
||||
else
|
||||
{
|
||||
ss_dassert(false);
|
||||
MXS_ERROR("%lu [gw_get_shared_session_auth_info] Couldn't get "
|
||||
"session authentication info. Session in a wrong state %d.",
|
||||
pthread_self(), dcb->session->state);
|
||||
rval = false;
|
||||
}
|
||||
spinlock_release(&dcb->session->ses_lock);
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Authentication of backend - read the reply, or handle an error
|
||||
*
|
||||
@ -829,7 +653,7 @@ gw_reply_on_error(DCB *dcb, mxs_auth_state_t state)
|
||||
}
|
||||
else
|
||||
{
|
||||
/** A NULL router_session can valid for a router if it declared the
|
||||
/** A NULL router_session is valid if a router declares the
|
||||
* RCAP_TYPE_NO_RSESSION capability flag */
|
||||
dcb->dcb_errhandle_called = true;
|
||||
}
|
||||
@ -837,6 +661,286 @@ 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);
|
||||
|
||||
const char* auth_plugin_name = dcb->authfunc.plugin_name ?
|
||||
dcb->authfunc.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 && strlen(local_session.db))
|
||||
{
|
||||
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
|
||||
*
|
||||
@ -869,12 +973,12 @@ gw_read_and_write(DCB *dcb)
|
||||
"Read from backend failed");
|
||||
|
||||
session->service->router->handleError(
|
||||
session->service->router_instance,
|
||||
session->router_session,
|
||||
errbuf,
|
||||
dcb,
|
||||
ERRACT_NEW_CONNECTION,
|
||||
&succp);
|
||||
session->service->router_instance,
|
||||
session->router_session,
|
||||
errbuf,
|
||||
dcb,
|
||||
ERRACT_NEW_CONNECTION,
|
||||
&succp);
|
||||
gwbuf_free(errbuf);
|
||||
|
||||
if (!succp)
|
||||
@ -970,22 +1074,21 @@ gw_read_and_write(DCB *dcb)
|
||||
gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL);
|
||||
|
||||
session->service->router->clientReply(
|
||||
session->service->router_instance,
|
||||
session->router_session,
|
||||
read_buffer,
|
||||
dcb);
|
||||
session->service->router_instance,
|
||||
session->router_session,
|
||||
read_buffer,
|
||||
dcb);
|
||||
return_code = 1;
|
||||
}
|
||||
}
|
||||
else if (dcb->session->client_dcb->dcb_role == DCB_ROLE_INTERNAL)
|
||||
{
|
||||
gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL);
|
||||
|
||||
session->service->router->clientReply(
|
||||
session->service->router_instance,
|
||||
session->router_session,
|
||||
read_buffer,
|
||||
dcb);
|
||||
session->service->router_instance,
|
||||
session->router_session,
|
||||
read_buffer,
|
||||
dcb);
|
||||
return_code = 1;
|
||||
}
|
||||
}
|
||||
@ -1999,175 +2102,6 @@ gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive the MySQL authentication packet from backend, packet # is 2
|
||||
*
|
||||
* @param protocol The MySQL protocol structure
|
||||
* @return -1 in case of failure, 1 if authentication was successful.
|
||||
*/
|
||||
static mxs_auth_state_t
|
||||
gw_read_auth_response(DCB *dcb, GWBUF *buffer)
|
||||
{
|
||||
mxs_auth_state_t rval = MXS_AUTH_STATE_FAILED;
|
||||
uint8_t cmd;
|
||||
|
||||
if (gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, 1, &cmd) && cmd == MYSQL_REPLY_OK)
|
||||
{
|
||||
rval = MXS_AUTH_STATE_COMPLETE;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
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("mysql_native_password");
|
||||
bytes++;
|
||||
|
||||
// the packet header
|
||||
bytes += 4;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
load_hashed_password(MySQLProtocol *conn, 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(conn->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;
|
||||
}
|
||||
|
||||
static void inline
|
||||
close_socket(int sock)
|
||||
{
|
||||
|
@ -562,12 +562,6 @@ int gw_read_client_event(DCB* dcb)
|
||||
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
|
||||
@ -577,18 +571,15 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
||||
* data extraction succeeds, then a call is made to the actual
|
||||
* authenticate function to carry out the user checks.
|
||||
*/
|
||||
if (MXS_AUTH_SUCCEEDED == (
|
||||
auth_val = dcb->authfunc.extract(dcb, read_buffer)))
|
||||
int auth_val = dcb->authfunc.extract(dcb, read_buffer);
|
||||
|
||||
if (MXS_AUTH_SUCCEEDED == auth_val)
|
||||
{
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
MySQLProtocol *protocol = (MySQLProtocol *)dcb->protocol;
|
||||
|
||||
/**
|
||||
* At this point, if the auth_val return code indicates success
|
||||
* the user authentication has been successfully completed.
|
||||
|
@ -52,6 +52,8 @@
|
||||
#include <netinet/tcp.h>
|
||||
#include <modutil.h>
|
||||
|
||||
uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN] = "";
|
||||
|
||||
static server_command_t* server_command_init(server_command_t* srvcmd, mysql_server_cmd_t cmd);
|
||||
|
||||
/**
|
||||
@ -1025,3 +1027,35 @@ bool read_complete_packet(DCB *dcb, GWBUF **readbuf)
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy shared session authentication info
|
||||
*
|
||||
* @param dcb A backend DCB
|
||||
* @param session Destination where authentication data is copied
|
||||
* @return bool true = success, false = fail
|
||||
*/
|
||||
bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session)
|
||||
{
|
||||
bool rval = true;
|
||||
CHK_DCB(dcb);
|
||||
CHK_SESSION(dcb->session);
|
||||
|
||||
spinlock_acquire(&dcb->session->ses_lock);
|
||||
|
||||
if (dcb->session->state != SESSION_STATE_ALLOC &&
|
||||
dcb->session->state != SESSION_STATE_DUMMY)
|
||||
{
|
||||
memcpy(session, dcb->session->client_dcb->data, sizeof(MYSQL_session));
|
||||
}
|
||||
else
|
||||
{
|
||||
ss_dassert(false);
|
||||
MXS_ERROR("%lu [gw_get_shared_session_auth_info] Couldn't get "
|
||||
"session authentication info. Session in a wrong state %d.",
|
||||
pthread_self(), dcb->session->state);
|
||||
rval = false;
|
||||
}
|
||||
spinlock_release(&dcb->session->ses_lock);
|
||||
return rval;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user