diff --git a/server/core/dcb.c b/server/core/dcb.c index c0f568f25..74f49b539 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -397,10 +397,10 @@ 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) + if (dcb->authfunc.destroy) { - dcb->authfunc.free(dcb); - dcb->backend_data = NULL; + dcb->authfunc.destroy(dcb->authenticator_data); + dcb->authenticator_data = NULL; } if (dcb->protoname) { @@ -814,6 +814,12 @@ dcb_connect(SERVER *server, SESSION *session, const char *protocol) memcpy(&dcb->authfunc, authfuncs, sizeof(GWAUTHENTICATOR)); + /** Allocate DCB specific authentication data */ + if (dcb->authfunc.create) + { + dcb->authenticator_data = dcb->authfunc.create(); + } + /** * Link dcb to session. Unlink is called in dcb_final_free */ @@ -3172,6 +3178,13 @@ dcb_accept(DCB *listener, GWPROTOCOL *protocol_funcs) } } memcpy(&(client_dcb->authfunc), authfuncs, sizeof(GWAUTHENTICATOR)); + + /** Allocate DCB specific authentication data */ + if (client_dcb->authfunc.create) + { + client_dcb->authenticator_data = client_dcb->authfunc.create(); + } + if (client_dcb->service->max_connections && client_dcb->service->client_count >= client_dcb->service->max_connections) { diff --git a/server/core/service.c b/server/core/service.c index 10535c157..b4304e238 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -288,6 +288,11 @@ serviceStartPort(SERVICE *service, SERV_LISTENER *port) memcpy(&port->listener->authfunc, authfuncs, sizeof(GWAUTHENTICATOR)); + /** + * Normally, we'd allocate the DCB specific authentication data. As the + * listeners aren't normal DCBs, we can skip that. + */ + if (port->address) { sprintf(config_bind, "%s:%d", port->address, port->port); @@ -1457,7 +1462,8 @@ int service_refresh_users(SERVICE *service) for (SERV_LISTENER *port = service->ports; port; port = port->next) { - if (port->listener->authfunc.loadusers(port) != MXS_AUTH_LOADUSERS_OK) + if (port->listener->authfunc.loadusers && + port->listener->authfunc.loadusers(port) != MXS_AUTH_LOADUSERS_OK) { MXS_ERROR("[%s] Failed to load users for listener '%s', authentication might not work.", service->name, port->name); diff --git a/server/include/dcb.h b/server/include/dcb.h index 29c934829..679ebb4cf 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -252,8 +252,8 @@ typedef struct dcb struct dcb *nextpersistent; /**< Next DCB in the persistent pool for SERVER */ 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 */ + void *data; /**< Specific client data, shared between DCBs of this session */ + void *authenticator_data; /**< The authenticator data for this DCB */ 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 */ @@ -284,7 +284,7 @@ typedef struct dcb .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, \ - .backend_data = NULL} + .authenticator_data = NULL} /** * The DCB usage filer used for returning DCB's in use for a certain reason diff --git a/server/include/gw_authenticator.h b/server/include/gw_authenticator.h index ee66c1764..24c06b532 100644 --- a/server/include/gw_authenticator.h +++ b/server/include/gw_authenticator.h @@ -42,12 +42,14 @@ struct servlistener; * @verbatim * The operations that can be performed on the descriptor * + * create Create a data structure unique to this DCB, stored in dcb->authenticator_data * extract Extract the data from a buffer and place in a structure + * shared at the session level, stored in dcb->data * connectssl Determine whether the connection can support SSL * authenticate Carry out the authentication * free Free extracted data + * destroy Destroy the unique DCB data * loadusers Load or update authenticator user data - * plugin_name The protocol specific name of the authentication plugin. * @endverbatim * * This forms the "module object" for authenticator modules within the gateway. @@ -56,12 +58,13 @@ struct servlistener; */ typedef struct gw_authenticator { + void *(*create)(); int (*extract)(struct dcb *, GWBUF *); bool (*connectssl)(struct dcb *); int (*authenticate)(struct dcb *); void (*free)(struct dcb *); + void (*destroy)(void *); int (*loadusers)(struct servlistener *); - const char* plugin_name; } GWAUTHENTICATOR; /** Return values for extract and authenticate entry points */ diff --git a/server/modules/authenticator/cdc_plain_auth.c b/server/modules/authenticator/cdc_plain_auth.c index 3b6fc93ed..bdecec83f 100644 --- a/server/modules/authenticator/cdc_plain_auth.c +++ b/server/modules/authenticator/cdc_plain_auth.c @@ -67,12 +67,13 @@ extern char *decryptPassword(char *crypt); */ static GWAUTHENTICATOR MyObject = { - cdc_auth_set_protocol_data, /* Extract data into structure */ - cdc_auth_is_client_ssl_capable, /* Check if client supports SSL */ - cdc_auth_authenticate, /* Authenticate user credentials */ - cdc_auth_free_client_data, /* Free the client data held in DCB */ - cdc_replace_users, - NULL + NULL, /* No create entry point */ + cdc_auth_set_protocol_data, /* Extract data into structure */ + cdc_auth_is_client_ssl_capable, /* Check if client supports SSL */ + cdc_auth_authenticate, /* Authenticate user credentials */ + cdc_auth_free_client_data, /* Free the client data held in DCB */ + NULL, /* No destroy entry point */ + cdc_replace_users /* Load CDC users */ }; static int cdc_auth_check( diff --git a/server/modules/authenticator/http_auth.c b/server/modules/authenticator/http_auth.c index 9d902afbd..981f7148a 100644 --- a/server/modules/authenticator/http_auth.c +++ b/server/modules/authenticator/http_auth.c @@ -59,12 +59,13 @@ static void http_auth_free_client_data(DCB *dcb); */ static GWAUTHENTICATOR MyObject = { - http_auth_set_protocol_data, /* Extract data into structure */ - http_auth_is_client_ssl_capable, /* Check if client supports SSL */ - http_auth_authenticate, /* Authenticate user credentials */ - http_auth_free_client_data, /* Free the client data held in DCB */ - users_default_loadusers, - NULL + NULL, /* No create entry point */ + http_auth_set_protocol_data, /* Extract data into structure */ + http_auth_is_client_ssl_capable, /* Check if client supports SSL */ + http_auth_authenticate, /* Authenticate user credentials */ + http_auth_free_client_data, /* Free the client data held in DCB */ + NULL, /* No destroy entry point */ + users_default_loadusers /* Load generic users */ }; typedef struct http_auth diff --git a/server/modules/authenticator/max_admin_auth.c b/server/modules/authenticator/max_admin_auth.c index ce5b85e5d..42f3fef03 100644 --- a/server/modules/authenticator/max_admin_auth.c +++ b/server/modules/authenticator/max_admin_auth.c @@ -59,12 +59,13 @@ static void max_admin_auth_free_client_data(DCB *dcb); */ static GWAUTHENTICATOR MyObject = { - max_admin_auth_set_protocol_data, /* Extract data into structure */ - max_admin_auth_is_client_ssl_capable, /* Check if client supports SSL */ - max_admin_auth_authenticate, /* Authenticate user credentials */ - max_admin_auth_free_client_data, /* Free the client data held in DCB */ - users_default_loadusers, - NULL + NULL, /* No create entry point */ + max_admin_auth_set_protocol_data, /* Extract data into structure */ + max_admin_auth_is_client_ssl_capable, /* Check if client supports SSL */ + max_admin_auth_authenticate, /* Authenticate user credentials */ + max_admin_auth_free_client_data, /* Free the client data held in DCB */ + NULL, /* No destroy entry point */ + users_default_loadusers /* Load generic users */ }; /** diff --git a/server/modules/authenticator/mysql_auth.c b/server/modules/authenticator/mysql_auth.c index 253b337f3..874640f0d 100644 --- a/server/modules/authenticator/mysql_auth.c +++ b/server/modules/authenticator/mysql_auth.c @@ -62,12 +62,13 @@ static int mysql_auth_load_users(SERV_LISTENER *port); */ static GWAUTHENTICATOR MyObject = { - mysql_auth_set_protocol_data, /* Extract data into structure */ - mysql_auth_is_client_ssl_capable, /* Check if client supports SSL */ - mysql_auth_authenticate, /* Authenticate user credentials */ - mysql_auth_free_client_data, /* Free the client data held in DCB */ - mysql_auth_load_users, /* Load users from backend databases */ - "mysql_native_password" + NULL, /* No create entry point */ + mysql_auth_set_protocol_data, /* Extract data into structure */ + mysql_auth_is_client_ssl_capable, /* Check if client supports SSL */ + mysql_auth_authenticate, /* Authenticate user credentials */ + mysql_auth_free_client_data, /* Free the client data held in DCB */ + NULL, /* No destroy entry point */ + mysql_auth_load_users /* Load users from backend databases */ }; static int combined_auth_check( @@ -243,22 +244,8 @@ mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) protocol = DCB_PROTOCOL(dcb, MySQLProtocol); CHK_PROTOCOL(protocol); - if (dcb->data == NULL) - { - if (NULL == (client_data = (MYSQL_session *)MXS_CALLOC(1, sizeof(MYSQL_session)))) - { - return MXS_AUTH_FAILED; - } -#if defined(SS_DEBUG) - client_data->myses_chk_top = CHK_NUM_MYSQLSES; - client_data->myses_chk_tail = CHK_NUM_MYSQLSES; -#endif - dcb->data = client_data; - } - else - { - client_data = (MYSQL_session *)dcb->data; - } + + client_data = (MYSQL_session *)dcb->data; client_auth_packet_size = gwbuf_length(buf); @@ -311,7 +298,7 @@ mysql_auth_set_client_data( gwbuf_copy_data(buffer, 0, client_auth_packet_size, client_auth_packet); /* The numbers are the fixed elements in the client handshake packet */ - int auth_packet_base_size = 4 + 4 + 4 + 1 + 23; + int auth_packet_base_size = MYSQL_AUTH_PACKET_BASE_SIZE; int packet_length_used = 0; /* Take data from fixed locations first */ diff --git a/server/modules/authenticator/mysql_backend_auth.c b/server/modules/authenticator/mysql_backend_auth.c index 7b8b7111f..dbabdd651 100644 --- a/server/modules/authenticator/mysql_backend_auth.c +++ b/server/modules/authenticator/mysql_backend_auth.c @@ -50,7 +50,7 @@ typedef struct mysql_backend_auth * @brief Allocate a new mysql_backend_auth object * @return Allocated object or NULL if memory allocation failed */ -mysql_backend_auth_t* mba_alloc() +void* auth_backend_create() { mysql_backend_auth_t* mba = MXS_MALLOC(sizeof(*mba)); @@ -62,6 +62,18 @@ mysql_backend_auth_t* mba_alloc() return mba; } +/** + * @brief Free allocated mysql_backend_auth object + * @param data Allocated mysql_backend_auth object + */ +void auth_backend_destroy(void *data) +{ + if (data) + { + MXS_FREE(data); + } +} + /** * Receive the MySQL authentication packet from backend, packet # is 2 * @@ -95,9 +107,9 @@ auth_backend_extract(DCB *dcb, GWBUF *buf) { int rval = MXS_AUTH_FAILED; - if (dcb->backend_data || (dcb->backend_data = mba_alloc())) + if (dcb->authenticator_data) { - mysql_backend_auth_t *mba = (mysql_backend_auth_t*)dcb->backend_data; + mysql_backend_auth_t *mba = (mysql_backend_auth_t*)dcb->authenticator_data; switch (mba->state) { @@ -146,7 +158,7 @@ static int auth_backend_authenticate(DCB *dcb) { int rval = MXS_AUTH_FAILED; - mysql_backend_auth_t *mba = (mysql_backend_auth_t*)dcb->backend_data; + mysql_backend_auth_t *mba = (mysql_backend_auth_t*)dcb->authenticator_data; if (mba->state == MBA_SEND_RESPONSE) { @@ -192,16 +204,6 @@ 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 */ @@ -230,12 +232,13 @@ static char *version_str = "V1.0.0"; */ 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 + auth_backend_create, /* Create authenticator */ + auth_backend_extract, /* Extract data into structure */ + auth_backend_ssl, /* Check if client supports SSL */ + auth_backend_authenticate, /* Authenticate user credentials */ + NULL, /* The shared data is freed by the client DCB */ + auth_backend_destroy, /* Destroy authenticator */ + NULL /* We don't need to load users */ }; /** diff --git a/server/modules/authenticator/null_auth_allow.c b/server/modules/authenticator/null_auth_allow.c index ff3b595b5..463500f74 100644 --- a/server/modules/authenticator/null_auth_allow.c +++ b/server/modules/authenticator/null_auth_allow.c @@ -58,12 +58,13 @@ static void null_auth_free_client_data(DCB *dcb); */ static GWAUTHENTICATOR MyObject = { - null_auth_set_protocol_data, /* Extract data into structure */ - null_auth_is_client_ssl_capable, /* Check if client supports SSL */ - null_auth_authenticate, /* Authenticate user credentials */ - null_auth_free_client_data, /* Free the client data held in DCB */ - users_default_loadusers, - NULL + NULL, /* No create entry point */ + null_auth_set_protocol_data, /* Extract data into structure */ + null_auth_is_client_ssl_capable, /* Check if client supports SSL */ + null_auth_authenticate, /* Authenticate user credentials */ + null_auth_free_client_data, /* Free the client data held in DCB */ + NULL, /* No destroy entry point */ + users_default_loadusers /* Load generic users */ }; /** diff --git a/server/modules/authenticator/null_auth_deny.c b/server/modules/authenticator/null_auth_deny.c index 59d11edcf..cdebefe49 100644 --- a/server/modules/authenticator/null_auth_deny.c +++ b/server/modules/authenticator/null_auth_deny.c @@ -58,12 +58,13 @@ static void null_auth_free_client_data(DCB *dcb); */ static GWAUTHENTICATOR MyObject = { - null_auth_set_protocol_data, /* Extract data into structure */ - null_auth_is_client_ssl_capable, /* Check if client supports SSL */ - null_auth_authenticate, /* Authenticate user credentials */ - null_auth_free_client_data, /* Free the client data held in DCB */ - users_default_loadusers, - NULL + NULL, /* No create entry point */ + null_auth_set_protocol_data, /* Extract data into structure */ + null_auth_is_client_ssl_capable, /* Check if client supports SSL */ + null_auth_authenticate, /* Authenticate user credentials */ + null_auth_free_client_data, /* Free the client data held in DCB */ + NULL, /* No destroy entry point */ + users_default_loadusers /* Load generic users */ }; /** diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 41c8ca05c..c19292f9d 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -81,6 +81,9 @@ #define DEFAULT_MYSQL_AUTH_PLUGIN "mysql_native_password" +/** All authentication responses are at least this many bytes long */ +#define MYSQL_AUTH_PACKET_BASE_SIZE 36 + /** Maximum length of a MySQL packet */ #define MYSQL_PACKET_LENGTH_MAX 0x00ffffff @@ -297,6 +300,8 @@ typedef struct /* The following can be compared using memcmp to detect a null password */ extern uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN]; +MYSQL_session* mysql_session_alloc(); + MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd); void mysql_protocol_done (DCB* dcb); const char *gw_mysql_protocol_state2string(int state); diff --git a/server/modules/protocol/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQLBackend/mysql_backend.c index a89158cf5..dfaec2b07 100644 --- a/server/modules/protocol/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQLBackend/mysql_backend.c @@ -848,8 +848,12 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) 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; + /** + * 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); @@ -909,7 +913,7 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) } // if the db is not NULL append it - if (local_session.db && strlen(local_session.db)) + if (local_session.db[0]) { memcpy(payload, local_session.db, strlen(local_session.db)); payload += strlen(local_session.db); diff --git a/server/modules/protocol/MySQLClient/mysql_client.c b/server/modules/protocol/MySQLClient/mysql_client.c index 4cfdb54cd..2c021a4f4 100644 --- a/server/modules/protocol/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQLClient/mysql_client.c @@ -311,8 +311,12 @@ int MySQLSendHandshake(DCB* dcb) memcpy(mysql_plugin_data, server_scramble + 8, 12); - const char* plugin_name = dcb->authfunc.plugin_name ? - dcb->authfunc.plugin_name : DEFAULT_MYSQL_AUTH_PLUGIN; + /** + * Use the default authentication plugin name in the initial handshake. If the + * authenticator needs to change the authentication method, it should send + * an AuthSwitchRequest packet to the client. + */ + const char* plugin_name = DEFAULT_MYSQL_AUTH_PLUGIN; int plugin_name_len = strlen(plugin_name); mysql_payload_size = @@ -562,6 +566,13 @@ int gw_read_client_event(DCB* dcb) static int gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read) { + /** Allocate the shared session structure */ + if (dcb->data == NULL && (dcb->data = mysql_session_alloc()) == NULL) + { + dcb_close(dcb); + return 1; + } + /** * The first step in the authentication process is to extract the * relevant information from the buffer supplied and place it @@ -590,7 +601,17 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read) */ if (MXS_AUTH_SUCCEEDED == auth_val) { - SESSION *session; + if (dcb->user == NULL) + { + /** User authentication complete, copy the username to the DCB */ + MYSQL_session *ses = dcb->data; + if ((dcb->user = MXS_STRDUP(ses->user)) == NULL) + { + dcb_close(dcb); + gwbuf_free(read_buffer); + return 0; + } + } protocol->protocol_auth_state = MXS_AUTH_STATE_RESPONSE_SENT; /** @@ -600,7 +621,7 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read) * is changed so that future data will go through the * normal data handling function instead of this one. */ - session = session_alloc(dcb->service, dcb); + SESSION *session = session_alloc(dcb->service, dcb); if (session != NULL) { diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 1e6989af9..c0c416b67 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -56,6 +56,25 @@ uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN] = ""; static server_command_t* server_command_init(server_command_t* srvcmd, mysql_server_cmd_t cmd); +/** + * @brief Allocate a new MySQL_session + * @return New MySQL_session or NULL if memory allocation failed + */ +MYSQL_session* mysql_session_alloc() +{ + MYSQL_session *ses = MXS_CALLOC(1, sizeof(MYSQL_session)); + + if (ses) + { +#ifdef SS_DEBUG + ses->myses_chk_top = CHK_NUM_MYSQLSES; + ses->myses_chk_tail = CHK_NUM_MYSQLSES; +#endif + } + + return ses; +} + /** * Creates MySQL protocol structure *