diff -rua mysql-proxy-0.8.3.orig/src//network-mysqld.c mysql-proxy-0.8.3/src//network-mysqld.c --- mysql-proxy-0.8.3.orig/src//network-mysqld.c 2012-08-06 13:42:12.000000000 +0200 +++ mysql-proxy-0.8.3/src//network-mysqld.c 2012-10-10 13:42:38.000000000 +0200 @@ -116,1916 +116,2376 @@ #define C(x) x, sizeof(x) - 1 #define S(x) x->str, x->len -/** - * call the cleanup callback for the current connection - * - * @param srv global context - * @param con connection context - * - * @return NETWORK_SOCKET_SUCCESS on success - */ -network_socket_retval_t plugin_call_cleanup(chassis *srv, network_mysqld_con *con) { - NETWORK_MYSQLD_PLUGIN_FUNC(func) = NULL; - network_socket_retval_t retval = NETWORK_SOCKET_SUCCESS; +#define WAIT_FOR_EVENT(ev_struct, ev_type, timeout) \ + event_set(&(ev_struct->event), ev_struct->fd, ev_type, network_mysqld_con_handle, user_data); \ + chassis_event_add_with_timeout(srv, &(ev_struct->event), timeout); - func = con->plugins.con_cleanup; - - if (!func) return retval; - LOCK_LUA(srv->priv->sc); - retval = (*func)(srv, con); - UNLOCK_LUA(srv->priv->sc); +// static function +static void +network_mysqld_con_handle_evread (int, network_mysqld_con *); - return retval; -} +static void +network_mysqld_con_state_error (int, network_mysqld_con *, short); -/** - * call the timeout callback for the current connection - * - * @param srv global context - * @param con connection context - * - * @return NETWORK_SOCKET_SUCCESS on success - */ -static network_socket_retval_t -plugin_call_timeout(chassis *srv, network_mysqld_con *con) { - NETWORK_MYSQLD_PLUGIN_FUNC(func) = NULL; - network_socket_retval_t retval = NETWORK_SOCKET_ERROR; +static void +network_mysqld_con_state_close (int, network_mysqld_con *); - func = con->plugins.con_timeout; - - if (!func) { - /* default implementation */ - g_debug("%s: connection between %s and %s timed out. closing it", - G_STRLOC, - con->client ? con->client->src->name->str : "(client)", - con->server ? con->server->dst->name->str : "(server)"); - con->state = CON_STATE_ERROR; - return NETWORK_SOCKET_SUCCESS; - } +static void +network_mysqld_con_state_init (int, network_mysqld_con *); - LOCK_LUA(srv->priv->sc); - retval = (*func)(srv, con); - UNLOCK_LUA(srv->priv->sc); +static int +network_mysqld_con_state_connect_server (int, network_mysqld_con *, network_mysqld_con_state_t *); - return retval; -} +static int +network_mysqld_con_state_read_handshake (int, network_mysqld_con *, network_mysqld_con_state_t); +static int +network_mysqld_con_state_send_handshake (int, network_mysqld_con *, network_mysqld_con_state_t); -chassis_private *network_mysqld_priv_init(void) { - chassis_private *priv; +static int +network_mysqld_con_state_read_auth (int, network_mysqld_con *, network_mysqld_con_state_t); - priv = g_new0(chassis_private, 1); +static int +network_mysqld_con_state_send_auth (int, network_mysqld_con *, network_mysqld_con_state_t); - priv->cons = g_ptr_array_new(); - priv->sc = lua_scope_new(); - priv->backends = network_backends_new(); +static int +network_mysqld_con_state_read_auth_result (int, network_mysqld_con *, network_mysqld_con_state_t); - return priv; -} +static int +network_mysqld_con_state_send_auth_result (int, network_mysqld_con *, network_mysqld_con_state_t); -void network_mysqld_priv_shutdown(chassis *chas, chassis_private *priv) { - if (!priv) return; +static int +network_mysqld_con_state_read_auth_old_password (int, network_mysqld_con *, network_mysqld_con_state_t); - /* network_mysqld_con_free() changes the priv->cons directly - * - * always free the first element until all are gone - */ - while (0 != priv->cons->len) { - network_mysqld_con *con = priv->cons->pdata[0]; +static int +network_mysqld_con_state_send_auth_old_password (int, network_mysqld_con *, network_mysqld_con_state_t); - plugin_call_cleanup(chas, con); - network_mysqld_con_free(con); - } -} +static int +network_mysqld_con_state_read_query (int, network_mysqld_con *, network_mysqld_con_state_t); -void network_mysqld_priv_free(chassis G_GNUC_UNUSED *chas, chassis_private *priv) { - if (!priv) return; +static int +network_mysqld_con_state_send_query (int, network_mysqld_con *, network_mysqld_con_state_t); - g_ptr_array_free(priv->cons, TRUE); +static int +network_mysqld_con_state_read_query_result (int, network_mysqld_con *, network_mysqld_con_state_t); - network_backends_free(priv->backends); +static int +network_mysqld_con_state_send_query_result (int, network_mysqld_con *, network_mysqld_con_state_t); - lua_scope_free(priv->sc); +static int +network_mysqld_con_state_read_local_infile_data (int, network_mysqld_con *, network_mysqld_con_state_t); - g_free(priv); -} +static int +network_mysqld_con_state_send_local_infile_data (int, network_mysqld_con *, network_mysqld_con_state_t); -int network_mysqld_init(chassis *srv) { - lua_State *L; - srv->priv_free = network_mysqld_priv_free; - srv->priv_shutdown = network_mysqld_priv_shutdown; - srv->priv = network_mysqld_priv_init(); +static int +network_mysqld_con_state_read_local_infile_result (int, network_mysqld_con *, network_mysqld_con_state_t); - /* store the pointer to the chassis in the Lua registry */ - L = srv->priv->sc->L; - lua_pushlightuserdata(L, (void*)srv); - lua_setfield(L, LUA_REGISTRYINDEX, CHASSIS_LUA_REGISTRY_KEY); - - return 0; -} +static int +network_mysqld_con_state_send_local_infile_result (int, network_mysqld_con *, network_mysqld_con_state_t); +static void +network_mysqld_con_state_send_error (int, network_mysqld_con *); + +static network_socket_retval_t +plugin_call_cleanup(chassis *, network_mysqld_con *); + +static network_socket_retval_t +plugin_call(chassis *, network_mysqld_con *, int); + +static int +network_mysqld_con_track_auth_result_state(network_mysqld_con *); -network_mysqld_con *network_mysqld_con_init() { - return network_mysqld_con_new(); -} /** - * create a connection - * - * @return a connection context + * Handle CON_STATE_SEND_ERROR */ -network_mysqld_con *network_mysqld_con_new() { - network_mysqld_con *con; - - con = g_new0(network_mysqld_con, 1); - con->timestamps = chassis_timestamps_new(); - con->parse.command = -1; - con->auth_switch_to_method = g_string_new(NULL); - con->auth_switch_to_round = 0; - con->auth_switch_to_data = g_string_new(NULL);; +void +network_mysqld_con_state_send_error (int event_fd, network_mysqld_con *con) { - /* some tiny helper macros */ -#define SECONDS ( 1 ) -#define MINUTES ( 60 * SECONDS ) -#define HOURS ( 60 * MINUTES ) - con->connect_timeout.tv_sec = 2 * SECONDS; - con->connect_timeout.tv_usec = 0; + chassis *srv = con->srv; + struct timeval timeout; + void *user_data = con; - con->read_timeout.tv_sec = 8 * HOURS; - con->read_timeout.tv_usec = 0; - - con->write_timeout.tv_sec = 8 * HOURS; - con->write_timeout.tv_usec = 0; -#undef SECONDS -#undef MINUTES -#undef HOURS + switch (network_mysqld_write(srv, con->client)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; - return con; -} + WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_error"); + return; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s.%d: network_mysqld_write(CON_STATE_SEND_ERROR) returned an error", __FILE__, __LINE__); -void network_mysqld_add_connection(chassis *srv, network_mysqld_con *con) { - con->srv = srv; + con->state = CON_STATE_ERROR; + break; + } - g_ptr_array_add(srv->priv->cons, con); + con->state = CON_STATE_CLOSE_CLIENT; } + /** - * free a connection - * - * closes the client and server sockets - * - * @param con connection context + * Handle CON_STATE_SEND_LOCAL_INFILE_RESULT */ -void network_mysqld_con_free(network_mysqld_con *con) { - if (!con) return; +int +network_mysqld_con_state_send_local_infile_result (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - if (con->parse.data && con->parse.data_free) { - con->parse.data_free(con->parse.data); - } + chassis *srv = con->srv; + struct timeval timeout; + void *user_data = con; + network_socket_retval_t call_ret; - if (con->server) network_socket_free(con->server); - if (con->client) network_socket_free(con->client); + switch (network_mysqld_write(srv, con->client)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; - g_string_free(con->auth_switch_to_method, TRUE); - g_string_free(con->auth_switch_to_data, TRUE); + WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_load_infile_result"); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + /** + * writing failed, closing connection + */ + con->state = CON_STATE_ERROR; + break; + } - /* we are still in the conns-array */ + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - g_ptr_array_remove_fast(con->srv->priv->cons, con); - chassis_timestamps_free(con->timestamps); + switch ((call_ret = plugin_call(srv, con, con->state))) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s: plugin_call(%s) unexpected return value: %d", + G_STRLOC, + network_mysqld_con_state_get_name(ostate), + call_ret); - g_free(con); + con->state = CON_STATE_ERROR; + break; + } + + return 0; } -#if 0 -static void dump_str(const char *msg, const unsigned char *s, size_t len) { - GString *hex; - size_t i; - - hex = g_string_new(NULL); +/** + * Handle CON_STATE_READ_LOCAL_INFILE_RESULT + */ +int +network_mysqld_con_state_read_local_infile_result (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - for (i = 0; i < len; i++) { - g_string_append_printf(hex, "%02x", s[i]); + chassis *srv = con->srv; + struct timeval timeout; + network_socket *recv_sock; + network_socket_retval_t call_ret; + void *user_data = con; - if ((i + 1) % 16 == 0) { - g_string_append(hex, "\n"); - } else { - g_string_append_c(hex, ' '); - } + recv_sock = con->server; + + switch (network_mysqld_read(srv, recv_sock)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; + + /* call us again when you have a event */ + WAIT_FOR_EVENT(recv_sock, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_load_infile_result"); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s: network_mysqld_read(%s) returned an error", + G_STRLOC, + network_mysqld_con_state_get_name(ostate)); + con->state = CON_STATE_ERROR; + break; } - g_message("(%s): %s", msg, hex->str); + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - g_string_free(hex, TRUE); -} -#endif + switch ((call_ret = plugin_call(srv, con, con->state))) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s: plugin_call(%s) unexpected return value: %d", + G_STRLOC, + network_mysqld_con_state_get_name(ostate), + call_ret); -int network_mysqld_queue_reset(network_socket *sock) { - sock->packet_id_is_reset = TRUE; + con->state = CON_STATE_ERROR; + break; + } return 0; } + /** - * synchronize the packet-ids of two network-sockets + * Handle CON_STATE_SEND_LOCAL_INFILE_DATA */ -int network_mysqld_queue_sync(network_socket *dst, network_socket *src) { - g_assert_cmpint(src->packet_id_is_reset, ==, FALSE); +int +network_mysqld_con_state_send_local_infile_data (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - if (dst->packet_id_is_reset == FALSE) { - /* this shouldn't really happen */ + chassis *srv = con->srv; + struct timeval timeout; + network_socket_retval_t call_ret; + + void *user_data = con; + + switch (network_mysqld_write(srv, con->server)) { + case NETWORK_SOCKET_SUCCESS: + /* if we still haven't read all data from LDLI so we need to go back and read more + */ + if (!con->local_file_data_is_finished && con->server) { + con->state = CON_STATE_READ_LOCAL_INFILE_DATA; + } + /* we have read all data from LDLI so we need to read the LDLI result from the server + */ + else { + con->state = CON_STATE_READ_LOCAL_INFILE_RESULT; + } + + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; + + WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_load_infile_data"); + + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + /** + * writing failed, closing connection + */ + con->state = CON_STATE_ERROR; + break; } - dst->last_packet_id = src->last_packet_id - 1; + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ + + switch ((call_ret = plugin_call(srv, con, con->state))) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s: plugin_call(%s) unexpected return value: %d", + G_STRLOC, + network_mysqld_con_state_get_name(ostate), + call_ret); + + con->state = CON_STATE_ERROR; + break; + } return 0; } + /** - * appends a raw MySQL packet to the queue - * - * the packet is append the queue directly and shouldn't be used by the caller afterwards anymore - * and has to by in the MySQL Packet format + * Handle CON_STATE_READ_LOCAL_INFILE_DATA * */ -int network_mysqld_queue_append_raw(network_socket *sock, network_queue *queue, GString *data) { - guint32 packet_len; - guint8 packet_id; +int +network_mysqld_con_state_read_local_infile_data (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - /* check that the length header is valid */ - if (queue != sock->send_queue && - queue != sock->recv_queue) { - g_critical("%s: queue = %p doesn't belong to sock %p", - G_STRLOC, - (void *)queue, - (void *)sock); - return -1; - } + chassis *srv = con->srv; + struct timeval timeout; + network_socket *recv_sock; + void *user_data = con; + network_socket_retval_t call_ret; - g_assert_cmpint(data->len, >=, 4); + recv_sock = con->client; - packet_len = network_mysqld_proto_get_packet_len(data); - packet_id = network_mysqld_proto_get_packet_id(data); + /** + * LDLI is usually a whole set of packets + */ + do { + // Read data from client + switch (network_mysqld_read(srv, recv_sock)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; + /* call us again when you have a event */ + WAIT_FOR_EVENT(recv_sock, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_load_infile_data"); - g_assert_cmpint(packet_len, ==, data->len - 4); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s: network_mysqld_read(%s) returned an error", + G_STRLOC, + network_mysqld_con_state_get_name(ostate)); + con->state = CON_STATE_ERROR; + break; + } - if (sock->packet_id_is_reset) { - /* the ->last_packet_id is undefined, accept what we get */ - sock->last_packet_id = packet_id; - sock->packet_id_is_reset = FALSE; - } else if (packet_id != (guint8)(sock->last_packet_id + 1)) { - sock->last_packet_id++; -#if 0 - g_critical("%s: packet-id %d doesn't match for socket's last packet %d, patching it", - G_STRLOC, - packet_id, - sock->last_packet_id); -#endif - network_mysqld_proto_set_packet_id(data, sock->last_packet_id); - } else { - sock->last_packet_id++; - } + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - network_queue_append(queue, data); + /** + * do the plugin call to decode the result-set to track if we are finished already + * or we need to keep reading the data + */ + switch ((call_ret = plugin_call(srv, con, con->state))) { + case NETWORK_SOCKET_SUCCESS: + /** + * if we still haven't read all data from LDLI, lets forward immediatly + * the data to the backends so that we can read the next packets + */ + if (!con->local_file_data_is_finished && con->server) { + con->state = CON_STATE_SEND_LOCAL_INFILE_DATA; + } + + break; + default: + g_critical("%s: plugin_call(%s) unexpected return value: %d", + G_STRLOC, + network_mysqld_con_state_get_name(ostate), + call_ret); + + con->state = CON_STATE_ERROR; + break; + } + /* read packets from the network until the plugin decodes to go to the next state */ + } while (con->state == CON_STATE_READ_LOCAL_INFILE_DATA); return 0; } /** - * appends a payload to the queue - * - * the packet is copied and prepened with the mysql packet header before it is appended to the queue - * if neccesary the payload is spread over multiple mysql packets + * Handle CON_STATE_SEND_QUERY_RESULT */ -int network_mysqld_queue_append(network_socket *sock, network_queue *queue, const char *data, size_t packet_len) { - gsize packet_offset = 0; - - do { - GString *s; - gsize cur_packet_len = MIN(packet_len, PACKET_LEN_MAX); - - s = g_string_sized_new(packet_len + 4); +int +network_mysqld_con_state_send_query_result (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { + chassis *srv = con->srv; + struct timeval timeout; + void *user_data = con; - if (sock->packet_id_is_reset) { - sock->packet_id_is_reset = FALSE; - sock->last_packet_id = 0xff; /** the ++last_packet_id will make sure we send a 0 */ - } + switch (network_mysqld_write(srv, con->client)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; - network_mysqld_proto_append_packet_len(s, cur_packet_len); - network_mysqld_proto_append_packet_id(s, ++sock->last_packet_id); - g_string_append_len(s, data + packet_offset, cur_packet_len); + WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_query_result"); - network_queue_append(queue, s); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + /** + * client is gone away + * + * close the connection and clean up + */ + con->state = CON_STATE_ERROR; + break; + } - if (packet_len == PACKET_LEN_MAX) { - s = g_string_sized_new(4); + /* if the write failed, don't call the plugin handlers */ + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - network_mysqld_proto_append_packet_len(s, 0); - network_mysqld_proto_append_packet_id(s, ++sock->last_packet_id); + /* in case we havn't read the full resultset from the server yet, go back and read more + */ + if (!con->resultset_is_finished && con->server) { + con->state = CON_STATE_READ_QUERY_RESULT; + return 0; + } - network_queue_append(queue, s); - } + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + con->state = CON_STATE_ERROR; + break; + } - packet_len -= cur_packet_len; - packet_offset += cur_packet_len; - } while (packet_len > 0); + /* special treatment for the LOAD DATA LOCAL INFILE command */ + if (con->state != CON_STATE_ERROR && + con->parse.command == COM_QUERY && + 1 == network_mysqld_com_query_result_is_local_infile(con->parse.data)) { + con->state = CON_STATE_READ_LOCAL_INFILE_DATA; + } return 0; } /** - * create a OK packet and append it to the send-queue - * - * @param con a client socket - * @param affected_rows affected rows - * @param insert_id insert_id - * @param server_status server_status (bitfield of SERVER_STATUS_*) - * @param warnings number of warnings to fetch with SHOW WARNINGS - * @return 0 - * - * @todo move to network_mysqld_proto + * Handle CON_STATE_READ_QUERY_RESULT */ -int network_mysqld_con_send_ok_full(network_socket *con, guint64 affected_rows, guint64 insert_id, guint16 server_status, guint16 warnings ) { - GString *packet = g_string_new(NULL); - network_mysqld_ok_packet_t *ok_packet; +int +network_mysqld_con_state_read_query_result (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - ok_packet = network_mysqld_ok_packet_new(); - ok_packet->affected_rows = affected_rows; - ok_packet->insert_id = insert_id; - ok_packet->server_status = server_status; - ok_packet->warnings = warnings; + network_socket *recv_sock; + struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; - network_mysqld_proto_append_ok_packet(packet, ok_packet); - - network_mysqld_queue_append(con, con->send_queue, S(packet)); - network_mysqld_queue_reset(con); + do { - g_string_free(packet, TRUE); - network_mysqld_ok_packet_free(ok_packet); + recv_sock = con->server; + + switch (network_mysqld_read(srv, recv_sock)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; + + WAIT_FOR_EVENT(con->server, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_query_result"); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_QUERY_RESULT) returned an error", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ + + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + /* if we don't need the resultset, forward it to the client */ + if (!con->resultset_is_finished && !con->resultset_is_needed) { + /* check how much data we have in the queue waiting, no need to try to send 5 bytes */ + if (con->client->send_queue->len > 64 * 1024) { + con->state = CON_STATE_SEND_QUERY_RESULT; + } + } + break; + case NETWORK_SOCKET_ERROR: + /* something nasty happend, let's close the connection */ + con->state = CON_STATE_ERROR; + break; + default: + g_critical("%s.%d: ...", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } + + + } while (con->state == CON_STATE_READ_QUERY_RESULT); return 0; } + /** - * send a simple OK packet - * - * - no affected rows - * - no insert-id - * - AUTOCOMMIT - * - no warnings - * - * @param con a client socket + * Handle CON_STATE_SEND_QUERY */ -int network_mysqld_con_send_ok(network_socket *con) { - return network_mysqld_con_send_ok_full(con, 0, 0, SERVER_STATUS_AUTOCOMMIT, 0); -} +int +network_mysqld_con_state_send_query (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { -static int network_mysqld_con_send_error_full_all(network_socket *con, - const char *errmsg, gsize errmsg_len, - guint errorcode, - const gchar *sqlstate, - gboolean is_41_protocol) { - GString *packet; - network_mysqld_err_packet_t *err_packet; + network_packet packet; + struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; - packet = g_string_sized_new(10 + errmsg_len); - - err_packet = is_41_protocol ? network_mysqld_err_packet_new() : network_mysqld_err_packet_new_pre41(); - err_packet->errcode = errorcode; - if (errmsg) g_string_assign_len(err_packet->errmsg, errmsg, errmsg_len); - if (sqlstate) g_string_assign_len(err_packet->sqlstate, sqlstate, strlen(sqlstate)); + if (con->server->send_queue->offset == 0) { + /* only parse the packets once */ - network_mysqld_proto_append_err_packet(packet, err_packet); + packet.data = g_queue_peek_head(con->server->send_queue->chunks); + packet.offset = 0; - network_mysqld_queue_append(con, con->send_queue, S(packet)); - network_mysqld_queue_reset(con); + if (0 != network_mysqld_con_command_states_init(con, &packet)) { + g_debug("%s: tracking mysql protocol states failed", + G_STRLOC); + con->state = CON_STATE_ERROR; - network_mysqld_err_packet_free(err_packet); - g_string_free(packet, TRUE); + return 0; + } + } + + switch (network_mysqld_write(srv, con->server)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; + + WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_query"); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_debug("%s.%d: network_mysqld_write(CON_STATE_SEND_QUERY) returned an error", + __FILE__, __LINE__); + + /** + * write() failed, close the connections + */ + con->state = CON_STATE_ERROR; + break; + } + + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ + + /* some statements don't have a server response */ + switch (con->parse.command) { + case COM_STMT_SEND_LONG_DATA: /* not acked */ + case COM_STMT_CLOSE: + con->state = CON_STATE_READ_QUERY; + if (con->client) network_mysqld_queue_reset(con->client); + if (con->server) network_mysqld_queue_reset(con->server); + break; + default: + con->state = CON_STATE_READ_QUERY_RESULT; + break; + } return 0; } /** - * send a error packet to the client connection - * - * @note the sqlstate has to match the SQL standard. If no matching SQL state is known, leave it at NULL - * - * @param con the client connection - * @param errmsg the error message - * @param errmsg_len byte-len of the error-message - * @param errorcode mysql error-code we want to send - * @param sqlstate if none-NULL, 5-char SQL state to send, if NULL, default SQL state is used - * - * @return 0 on success + * Handle CON_STATE_READ_QUERY */ -int network_mysqld_con_send_error_full(network_socket *con, const char *errmsg, gsize errmsg_len, guint errorcode, const gchar *sqlstate) { - return network_mysqld_con_send_error_full_all(con, errmsg, errmsg_len, errorcode, sqlstate, TRUE); -} +int +network_mysqld_con_state_read_query (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { + struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; -/** - * send a error-packet to the client connection - * - * errorcode is 1000, sqlstate is NULL - * - * @param con the client connection - * @param errmsg the error message - * @param errmsg_len byte-len of the error-message - * - * @see network_mysqld_con_send_error_full - */ -int network_mysqld_con_send_error(network_socket *con, const char *errmsg, gsize errmsg_len) { - return network_mysqld_con_send_error_full(con, errmsg, errmsg_len, ER_UNKNOWN_ERROR, NULL); -} + network_socket *recv_sock; + network_packet last_packet; -/** - * send a error packet to the client connection (pre-4.1 protocol) - * - * @param con the client connection - * @param errmsg the error message - * @param errmsg_len byte-len of the error-message - * @param errorcode mysql error-code we want to send - * - * @return 0 on success - */ -int network_mysqld_con_send_error_pre41_full(network_socket *con, const char *errmsg, gsize errmsg_len, guint errorcode) { - return network_mysqld_con_send_error_full_all(con, errmsg, errmsg_len, errorcode, NULL, FALSE); + recv_sock = con->client; + + do { + switch (network_mysqld_read(srv, recv_sock)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; + + WAIT_FOR_EVENT(con->client, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_query"); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_QUERY) returned an error", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + return 1; + break; + } + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ + + last_packet.data = g_queue_peek_tail(recv_sock->recv_queue->chunks); + + } while (last_packet.data->len == PACKET_LEN_MAX + NET_HEADER_SIZE); /* read all chunks of the overlong data */ + + if (con->server && + con->server->challenge && + con->server->challenge->server_version > 50113 && con->server->challenge->server_version < 50118) { + /** + * Bug #25371 + * + * COM_CHANGE_USER returns 2 ERR packets instead of one + * + * we can auto-correct the issue if needed and remove the second packet + * Some clients handle this issue and expect a double ERR packet. + */ + network_packet packet; + guint8 com; + + packet.data = g_queue_peek_head(recv_sock->recv_queue->chunks); + packet.offset = 0; + + if (0 == network_mysqld_proto_skip_network_header(&packet) && + 0 == network_mysqld_proto_get_int8(&packet, &com) && + com == COM_CHANGE_USER) { + network_mysqld_con_send_error(con->client, C("COM_CHANGE_USER is broken on 5.1.14-.17, please upgrade the MySQL Server")); + con->state = CON_STATE_SEND_QUERY_RESULT; + return 0; + } + } + + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s.%d: plugin_call(CON_STATE_READ_QUERY) failed", __FILE__, __LINE__); + + con->state = CON_STATE_ERROR; + break; + } + + /** + * there should be 3 possible next states from here: + * + * - CON_STATE_ERROR (if something went wrong and we want to close the connection + * - CON_STATE_SEND_QUERY (if we want to send data to the con->server) + * - CON_STATE_SEND_QUERY_RESULT (if we want to send data to the con->client) + * + * @todo verify this with a clean switch () + */ + + /* reset the tracked command + * + * if the plugin decided to send a result, it has to track the commands itself + * otherwise LOAD DATA LOCAL INFILE and friends will fail + */ + if (con->state == CON_STATE_SEND_QUERY) { + network_mysqld_con_reset_command_response_state(con); + } + + return 0; } /** - * send a error-packet to the client connection (pre-4.1 protocol) - * - * @param con the client connection - * @param errmsg the error message - * @param errmsg_len byte-len of the error-message - * - * @see network_mysqld_con_send_error_pre41_full + * Handle CON_STATE_SEND_AUTH_OLD_PASSWORD */ -int network_mysqld_con_send_error_pre41(network_socket *con, const char *errmsg, gsize errmsg_len) { - return network_mysqld_con_send_error_pre41_full(con, errmsg, errmsg_len, ER_UNKNOWN_ERROR); + int +network_mysqld_con_state_send_auth_old_password (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) +{ + struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; + + switch (network_mysqld_write(srv, con->server)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; + + WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_auth_old_password"); + + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + /* might be a connection close, we should just close the connection and be happy */ + g_debug("%s.%d: network_mysqld_write(CON_STATE_SEND_AUTH_OLD_PASSWORD) returned an error", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ + + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s.%d: plugin_call(CON_STATE_SEND_AUTH_OLD_PASSWORD) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } + + return 0; } /** - * get a full packet from the raw queue and move it to the packet queue + * Handle CON_STATE_READ_AUTH_OLD_PASSWORD */ -network_socket_retval_t network_mysqld_con_get_packet(chassis G_GNUC_UNUSED*chas, network_socket *con) { - GString *packet = NULL; - GString header; - char header_str[NET_HEADER_SIZE + 1] = ""; - guint32 packet_len; - guint8 packet_id; +int +network_mysqld_con_state_read_auth_old_password (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { + struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; - /** - * read the packet header (4 bytes) - */ - header.str = header_str; - header.allocated_len = sizeof(header_str); - header.len = 0; + switch (network_mysqld_read(srv, con->client)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; - /* read the packet len if the leading packet */ - if (!network_queue_peek_string(con->recv_queue_raw, NET_HEADER_SIZE, &header)) { - /* too small */ + WAIT_FOR_EVENT(con->client, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_auth_old_password"); - return NETWORK_SOCKET_WAIT_FOR_EVENT; + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_AUTH_OLD_PASSWORD) returned an error", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + return 1; + break; } - packet_len = network_mysqld_proto_get_packet_len(&header); - packet_id = network_mysqld_proto_get_packet_id(&header); - - /* move the packet from the raw queue to the recv-queue */ - if ((packet = network_queue_pop_string(con->recv_queue_raw, packet_len + NET_HEADER_SIZE, NULL))) { -#ifdef NETWORK_DEBUG_TRACE_IO - /* to trace the data we received from the socket, enable this */ - g_debug_hexdump(G_STRLOC, S(packet)); -#endif + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - if (con->packet_id_is_reset) { - con->last_packet_id = packet_id; - con->packet_id_is_reset = FALSE; - } else if (packet_id != (guint8)(con->last_packet_id + 1)) { - g_critical("%s: received packet-id %d, but expected %d ... out of sync.", - G_STRLOC, - packet_id, - con->last_packet_id + 1); - return NETWORK_SOCKET_ERROR; - } else { - con->last_packet_id = packet_id; - } - - network_queue_append(con->recv_queue, packet); - } else { - return NETWORK_SOCKET_WAIT_FOR_EVENT; + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s.%d: plugin_call(CON_STATE_READ_AUTH_OLD_PASSWORD) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; } - return NETWORK_SOCKET_SUCCESS; + return 0; } + /** - * read a MySQL packet from the socket - * - * the packet is added to the con->recv_queue and contains a full mysql packet - * with packet-header and everything + * Handle CON_STATE_SEND_AUTH_RESULT */ -network_socket_retval_t network_mysqld_read(chassis G_GNUC_UNUSED*chas, network_socket *con) { - switch (network_socket_read(con)) { - case NETWORK_SOCKET_WAIT_FOR_EVENT: - return NETWORK_SOCKET_WAIT_FOR_EVENT; - case NETWORK_SOCKET_ERROR: - return NETWORK_SOCKET_ERROR; - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_ERROR_RETRY: - g_error("NETWORK_SOCKET_ERROR_RETRY wasn't expected"); - break; - } +int +network_mysqld_con_state_send_auth_result (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - return network_mysqld_con_get_packet(chas, con); -} + struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; -network_socket_retval_t network_mysqld_write(chassis G_GNUC_UNUSED*chas, network_socket *con) { - network_socket_retval_t ret; + switch (network_mysqld_write(srv, con->client)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; - ret = network_socket_write(con, -1); + WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_auth_result"); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_debug("%s.%d: network_mysqld_write(CON_STATE_SEND_AUTH_RESULT) returned an error", __FILE__, __LINE__); - return ret; + con->state = CON_STATE_ERROR; + break; + } + + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ + + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s.%d: ...", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } + + return 0; } /** - * call the hooks of the plugins for each state - * - * if the plugin doesn't implement a hook, we provide a default operation - * - * @param srv the global context - * @param con the connection context - * @param state state to handle - * @return NETWORK_SOCKET_SUCCESS on success + * Handle CON_STATE_READ_AUTH_RESULT */ -network_socket_retval_t plugin_call(chassis *srv, network_mysqld_con *con, int state) { - network_socket_retval_t ret; - NETWORK_MYSQLD_PLUGIN_FUNC(func) = NULL; +int +network_mysqld_con_state_read_auth_result (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - switch (state) { - case CON_STATE_INIT: - func = con->plugins.con_init; + struct timeval timeout; + chassis *srv = con->srv; + network_socket *recv_sock; + recv_sock = con->server; + void *user_data = con; - if (!func) { /* default implementation */ - con->state = CON_STATE_CONNECT_SERVER; - } - break; - case CON_STATE_CONNECT_SERVER: - func = con->plugins.con_connect_server; - if (!func) { /* default implementation */ - con->state = CON_STATE_READ_HANDSHAKE; - } + switch (network_mysqld_read(srv, recv_sock)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; - break; - case CON_STATE_SEND_HANDSHAKE: - func = con->plugins.con_send_handshake; + WAIT_FOR_EVENT(con->server, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_auth_result"); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_AUTH_RESULT) returned an error", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } - if (!func) { /* default implementation */ - con->state = CON_STATE_READ_AUTH; - } + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - break; - case CON_STATE_READ_HANDSHAKE: - func = con->plugins.con_read_handshake; + if (0 != network_mysqld_con_track_auth_result_state(con)) { + con->state = CON_STATE_ERROR; + return 0; + } - break; - case CON_STATE_READ_AUTH: - func = con->plugins.con_read_auth; + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s.%d: plugin_call(CON_STATE_READ_AUTH_RESULT) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); - break; - case CON_STATE_SEND_AUTH: - func = con->plugins.con_send_auth; + con->state = CON_STATE_ERROR; + break; + } - if (!func) { /* default implementation */ - con->state = CON_STATE_READ_AUTH_RESULT; - } - break; - case CON_STATE_READ_AUTH_RESULT: - func = con->plugins.con_read_auth_result; - break; - case CON_STATE_SEND_AUTH_RESULT: - /* called after the auth data is sent to the client */ - func = con->plugins.con_send_auth_result; - if (!func) { - /* - * figure out what to do next: - * - switch to 'read command from client' - * - close connection - * - read auth-data from client - * - read another auth-result packet from server - */ - switch (con->auth_result_state) { - case MYSQLD_PACKET_OK: - /* OK, delivered to client, switch to command phase */ - con->state = CON_STATE_READ_QUERY; - break; - case MYSQLD_PACKET_ERR: - /* ERR delivered to client, close the connection now */ - con->state = CON_STATE_ERROR; - break; - case 0x01: /* more auth data */ - /** - * FIXME: we should track that the server only sends us a 0x01 reply if - * we first went through "switch auth packet" - */ + return 0; +} - /** - * if we switched to win-auth and SPNEGO is used, check if the response packet contains: - * - * negState = accept-succeeded. - */ - if ((strleq(S(con->auth_switch_to_method), C("authentication_windows_client"))) && - con->auth_next_packet_is_from_server) { - /* we either have SPNEGO or NTLM */ - con->state = CON_STATE_READ_AUTH_RESULT; - break; - } - case MYSQLD_PACKET_EOF: - /* - * next, read the auth data from the client - */ - con->state = CON_STATE_READ_AUTH_OLD_PASSWORD; - break; - default: - g_debug("%s.%d: unexpected state for SEND_AUTH_RESULT: %02x", - __FILE__, __LINE__, - con->auth_result_state); - con->state = CON_STATE_ERROR; - break; - } - } - break; - case CON_STATE_READ_AUTH_OLD_PASSWORD: - func = con->plugins.con_read_auth_old_password; - - if (!func) { - network_socket *recv_sock, *send_sock; - network_packet packet; - guint32 packet_len; - - /* move the packet to the send queue */ - - recv_sock = con->client; - send_sock = con->server; +/** + * Handle CON_STATE_SEND_AUTH + */ +int +network_mysqld_con_state_send_auth (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - if (NULL == con->server) { - /** - * we have to auth against same backend as we did before - * but the user changed it - */ + struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; - g_message("%s.%d: (lua) read-auth-old-password failed as backend_ndx got reset.", __FILE__, __LINE__); + switch (network_mysqld_write(srv, con->server)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; - network_mysqld_con_send_error(con->client, C("(lua) read-auth-old-password failed as backend_ndx got reset.")); - con->state = CON_STATE_SEND_ERROR; - break; - } + WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_auth"); - packet.data = g_queue_peek_head(recv_sock->recv_queue->chunks); - packet.offset = 0; + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + /* might be a connection close, we should just close the connection and be happy */ + con->state = CON_STATE_ERROR; - packet_len = network_mysqld_proto_get_packet_len(packet.data); + break; + } - if ((strleq(S(con->auth_switch_to_method), C("authentication_windows_client"))) && - (con->auth_switch_to_round == 0) && - (packet_len == 255)) { -#if 1 - /** - * FIXME: the 2-packet win-auth protocol enhancements aren't properly tested yet. - * therefore they are disabled for now. - */ - g_string_free(g_queue_pop_head(recv_sock->recv_queue->chunks), TRUE); + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - network_mysqld_con_send_error(recv_sock, C("long packets for windows-authentication aren't completely handled yet. Please use another auth-method for now.")); + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s.%d: plugin_call(CON_STATE_SEND_AUTH) != NETWORK_SOCKET_SUCCESS", + __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } - con->state = CON_STATE_SEND_ERROR; -#else - con->auth_switch_to_round++; - /* move the packet to the send-queue - */ - network_mysqld_queue_append_raw(send_sock, send_sock->send_queue, - g_queue_pop_head(recv_sock->recv_queue->chunks)); + return 0; +} - /* stay in this state and read the next packet too */ -#endif - } else { - /* move the packet to the send-queue - */ - network_mysqld_queue_append_raw(send_sock, send_sock->send_queue, - g_queue_pop_head(recv_sock->recv_queue->chunks)); - con->state = CON_STATE_SEND_AUTH_OLD_PASSWORD; - } - } - break; - case CON_STATE_SEND_AUTH_OLD_PASSWORD: - /** - * data is at the server, read the response next - */ - con->state = CON_STATE_READ_AUTH_RESULT; - break; - case CON_STATE_READ_QUERY: - func = con->plugins.con_read_query; - break; - case CON_STATE_READ_QUERY_RESULT: - func = con->plugins.con_read_query_result; - break; - case CON_STATE_SEND_QUERY_RESULT: - func = con->plugins.con_send_query_result; +/** + * Handle CON_STATE_READ_AUTH + */ +int +network_mysqld_con_state_read_auth (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - if (!func) { /* default implementation */ - con->state = CON_STATE_READ_QUERY; - } - break; + struct timeval timeout; + chassis *srv = con->srv; + network_socket *recv_sock = NULL; + void *user_data = con; - case CON_STATE_SEND_LOCAL_INFILE_DATA: - func = con->plugins.con_send_local_infile_data; + recv_sock = con->client; - if (!func) { /* default implementation */ - con->state = CON_STATE_READ_LOCAL_INFILE_RESULT; - } + switch (network_mysqld_read(srv, recv_sock)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; - break; - case CON_STATE_READ_LOCAL_INFILE_DATA: - func = con->plugins.con_read_local_infile_data; + WAIT_FOR_EVENT(con->client, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_auth"); - if (!func) { /* the plugins have to implement this function to track LOAD DATA LOCAL INFILE handling work */ + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_AUTH) returned an error", __FILE__, __LINE__); con->state = CON_STATE_ERROR; - } - - break; - case CON_STATE_SEND_LOCAL_INFILE_RESULT: - func = con->plugins.con_send_local_infile_result; - - if (!func) { /* default implementation */ - con->state = CON_STATE_READ_QUERY; - } + break; + } - break; - case CON_STATE_READ_LOCAL_INFILE_RESULT: - func = con->plugins.con_read_local_infile_result; + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - if (!func) { /* the plugins have to implement this function to track LOAD DATA LOCAL INFILE handling work */ + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_ERROR: + con->state = CON_STATE_SEND_ERROR; + break; + default: + g_critical("%s.%d: plugin_call(CON_STATE_READ_AUTH) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); con->state = CON_STATE_ERROR; - } - - break; - case CON_STATE_ERROR: - g_debug("%s.%d: not executing plugin function in state CON_STATE_ERROR", __FILE__, __LINE__); - return NETWORK_SOCKET_SUCCESS; - default: - g_error("%s.%d: unhandled state: %d", - __FILE__, __LINE__, - state); + break; } - if (!func) return NETWORK_SOCKET_SUCCESS; - LOCK_LUA(srv->priv->sc); - ret = (*func)(srv, con); - UNLOCK_LUA(srv->priv->sc); - - return ret; + return 0; } /** - * reset the command-response parsing - * - * some commands needs state information and we have to - * reset the parsing as soon as we add a new command to the send-queue + * Handle CON_STATE_SEND_HANDSHAKE */ -void network_mysqld_con_reset_command_response_state(network_mysqld_con *con) { - con->parse.command = -1; - if (con->parse.data && con->parse.data_free) { - con->parse.data_free(con->parse.data); +int +network_mysqld_con_state_send_handshake (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { - con->parse.data = NULL; - con->parse.data_free = NULL; + chassis *srv = con->srv; + struct timeval timeout; + void *user_data = con; + + switch (network_mysqld_write(srv, con->client)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->write_timeout; + + WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_handshake"); + + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + /** + * writing failed, closing connection + */ + con->state = CON_STATE_ERROR; + break; } -} -/** - * get the name of a connection state - */ -const char *network_mysqld_con_state_get_name(network_mysqld_con_state_t state) { - switch (state) { - case CON_STATE_INIT: return "CON_STATE_INIT"; - case CON_STATE_CONNECT_SERVER: return "CON_STATE_CONNECT_SERVER"; - case CON_STATE_READ_HANDSHAKE: return "CON_STATE_READ_HANDSHAKE"; - case CON_STATE_SEND_HANDSHAKE: return "CON_STATE_SEND_HANDSHAKE"; - case CON_STATE_READ_AUTH: return "CON_STATE_READ_AUTH"; - case CON_STATE_SEND_AUTH: return "CON_STATE_SEND_AUTH"; - case CON_STATE_READ_AUTH_OLD_PASSWORD: return "CON_STATE_READ_AUTH_OLD_PASSWORD"; - case CON_STATE_SEND_AUTH_OLD_PASSWORD: return "CON_STATE_SEND_AUTH_OLD_PASSWORD"; - case CON_STATE_READ_AUTH_RESULT: return "CON_STATE_READ_AUTH_RESULT"; - case CON_STATE_SEND_AUTH_RESULT: return "CON_STATE_SEND_AUTH_RESULT"; - case CON_STATE_READ_QUERY: return "CON_STATE_READ_QUERY"; - case CON_STATE_SEND_QUERY: return "CON_STATE_SEND_QUERY"; - case CON_STATE_READ_QUERY_RESULT: return "CON_STATE_READ_QUERY_RESULT"; - case CON_STATE_SEND_QUERY_RESULT: return "CON_STATE_SEND_QUERY_RESULT"; - case CON_STATE_READ_LOCAL_INFILE_DATA: return "CON_STATE_READ_LOCAL_INFILE_DATA"; - case CON_STATE_SEND_LOCAL_INFILE_DATA: return "CON_STATE_SEND_LOCAL_INFILE_DATA"; - case CON_STATE_READ_LOCAL_INFILE_RESULT: return "CON_STATE_READ_LOCAL_INFILE_RESULT"; - case CON_STATE_SEND_LOCAL_INFILE_RESULT: return "CON_STATE_SEND_LOCAL_INFILE_RESULT"; - case CON_STATE_CLOSE_CLIENT: return "CON_STATE_CLOSE_CLIENT"; - case CON_STATE_CLOSE_SERVER: return "CON_STATE_CLOSE_SERVER"; - case CON_STATE_ERROR: return "CON_STATE_ERROR"; - case CON_STATE_SEND_ERROR: return "CON_STATE_SEND_ERROR"; + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ + + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + g_critical("%s.%d: plugin_call(CON_STATE_SEND_HANDSHAKE) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; } - return "unknown"; + return 0; } -static int network_mysqld_con_track_auth_result_state(network_mysqld_con *con) { - network_packet packet; - guint8 state; - int err = 0; +/** + * Handle CON_STATE_READ_HANDSHAKE + */ +int +network_mysqld_con_state_read_handshake (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t ostate) { /** - * depending on the result-set we have different exit-points - * - OK -> READ_QUERY - * - EOF -> (read old password hash) - * - ERR -> ERROR + * read auth data from the remote mysql-server */ - packet.data = g_queue_peek_head(con->server->recv_queue->chunks); - packet.offset = 0; + struct timeval timeout; + network_socket *recv_sock = NULL; + chassis *srv = con->srv; + void *user_data = con; - err = err || network_mysqld_proto_skip_network_header(&packet); - err = err || network_mysqld_proto_peek_int8(&packet, &state); + recv_sock = con->server; - if (err) return -1; + switch (network_mysqld_read(srv, recv_sock)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_WAIT_FOR_EVENT: + timeout = con->read_timeout; - con->auth_result_state = state; + /* call us again when you have a event */ + WAIT_FOR_EVENT(con->server, EV_READ, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_handshake"); - if (state == 0xfe) { - /* a long auth-switch packet */ - err = err || network_mysqld_proto_skip(&packet, 1); + return 1; + break; + case NETWORK_SOCKET_ERROR_RETRY: + case NETWORK_SOCKET_ERROR: + g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_HANDSHAKE) returned an error", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; + } - if (packet.data->len - packet.offset > 0) { - err = err || network_mysqld_proto_get_gstring(&packet, con->auth_switch_to_method); - err = err || network_mysqld_proto_get_gstring_len(&packet, packet.data->len - packet.offset, con->auth_switch_to_data); - } else { - /* just in case we get here switch ... which shouldn't happen ... */ - g_string_truncate(con->auth_switch_to_method, 0); - g_string_truncate(con->auth_switch_to_data, 0); - } - con->auth_switch_to_round = 0; - con->auth_next_packet_is_from_server = FALSE; - } else if (state == 0x01) { - if ((strleq(S(con->auth_switch_to_method), C("authentication_windows_client")))) { - GError *gerr = NULL; + if (con->state != ostate) return 0; /* the state has changed (e.g. CON_STATE_ERROR) */ - /* if the packet comes from the server, has a 0x01, is SPNEGO and has 'accept-completed' set, - * the next packet comes from the server too + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_ERROR: + /** + * we couldn't understand the pack from the server + * + * we have something in the queue and will send it to the client + * and close the connection afterwards */ - if (0 != network_mysqld_proto_skip(&packet, 1)) { - /* hmm ... what to do now ? */ - err = 1; - } else if (FALSE == network_asn1_is_valid(&packet, &gerr)) { - g_debug("%s: ASN1 packet is invalid: %s", G_STRLOC, gerr->message); - g_clear_error(&gerr); - err = 1; - } else { - network_spnego_response_token *token; - token = network_spnego_response_token_new(); - - if (TRUE == network_spnego_proto_get_response_token(&packet, token, &gerr)) { - if (token->negState != SPNEGO_RESPONSE_STATE_ACCEPT_INCOMPLETE) { - con->auth_next_packet_is_from_server = TRUE; - } - } else { - g_debug("%s: parsing spnego failed: %s", G_STRLOC, gerr->message); - /* do we care why it failed ? */ - g_clear_error(&gerr); - } + con->state = CON_STATE_SEND_ERROR; - network_spnego_response_token_free(token); - } - } + break; + default: + g_critical("%s.%d: ...", __FILE__, __LINE__); + con->state = CON_STATE_ERROR; + break; } - return err ? -1 : 0; + + return 0; } -/** - * handle the different states of the MySQL protocol - * - * @param event_fd fd on which the event was fired - * @param events the event that was fired - * @param user_data the connection handle +/** + * Handle CON_STATE_CONNECT_SERVER */ -void network_mysqld_con_handle(int event_fd, short events, void *user_data) { - network_mysqld_con_state_t ostate; - network_mysqld_con *con = user_data; +int +network_mysqld_con_state_connect_server (int event_fd, + network_mysqld_con *con, + network_mysqld_con_state_t *ostate) { + + struct timeval timeout; chassis *srv = con->srv; - int retval; + void *user_data = con; network_socket_retval_t call_ret; - g_assert(srv); - g_assert(con); + switch ((call_ret = plugin_call(srv, con, con->state))) { + case NETWORK_SOCKET_SUCCESS: - if (events == EV_READ) { - int b = -1; + /** + * hmm, if this is success and we have something in the clients send-queue + * we just send it out ... who needs a server ? */ - /** - * check how much data there is to read - * - * ioctl() - * - returns 0 if connection is closed - * - or -1 and ECONNRESET on solaris - * or -1 and EPIPE on HP/UX - */ - if (ioctl(event_fd, FIONREAD, &b)) { - switch (errno) { - case E_NET_CONNRESET: /* solaris */ - case EPIPE: /* hp/ux */ - if (con->client && event_fd == con->client->fd) { - /* the client closed the connection, let's keep the server side open */ - con->state = CON_STATE_CLOSE_CLIENT; - } else if (con->server && event_fd == con->server->fd && con->com_quit_seen) { - con->state = CON_STATE_CLOSE_SERVER; - } else { - /* server side closed on use, oops, close both sides */ - con->state = CON_STATE_ERROR; - } - break; - default: - g_critical("ioctl(%d, FIONREAD, ...) failed: %s", event_fd, g_strerror(errno)); + if ((con->client != NULL && con->client->send_queue->chunks->length > 0) && + con->server == NULL) { + /* we want to send something to the client */ - con->state = CON_STATE_ERROR; - break; - } - } else if (b != 0) { - if (con->client && event_fd == con->client->fd) { - con->client->to_read = b; - } else if (con->server && event_fd == con->server->fd) { - con->server->to_read = b; + con->state = CON_STATE_SEND_HANDSHAKE; } else { - g_error("%s.%d: neither nor", __FILE__, __LINE__); + g_assert(con->server); } - } else { /* Linux */ - if (con->client && event_fd == con->client->fd) { - /* the client closed the connection, let's keep the server side open */ - con->state = CON_STATE_CLOSE_CLIENT; - } else if (con->server && event_fd == con->server->fd && con->com_quit_seen) { - con->state = CON_STATE_CLOSE_SERVER; + + break; + case NETWORK_SOCKET_ERROR_RETRY: + if (con->server) { + timeout = con->connect_timeout; + /** + * we have a server connection waiting to begin writable + */ + WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::connect_server"); + return 1; } else { - /* server side closed on use, oops, close both sides */ - con->state = CON_STATE_ERROR; + /* try to get a connection to another backend, + * + * setting ostate = CON_STATE_INIT is a hack to make sure + * the loop is coming back to this function again */ + *ostate = CON_STATE_INIT; } - } - } else if (events == EV_TIMEOUT) { - /* if we got a timeout on CON_STATE_CONNECT_SERVER we should pick another backend */ - switch ((retval = plugin_call_timeout(srv, con))) { - case NETWORK_SOCKET_SUCCESS: - /* the plugin did set a reasonable next state */ + + break; + case NETWORK_SOCKET_ERROR: + /** + * connecting failed and no option to retry + * + * close the connection + */ + con->state = CON_STATE_SEND_ERROR; break; default: + g_critical("%s: hook for CON_STATE_CONNECT_SERVER return invalid return code: %d", + G_STRLOC, + call_ret); + con->state = CON_STATE_ERROR; + break; - } } -#define WAIT_FOR_EVENT(ev_struct, ev_type, timeout) \ - event_set(&(ev_struct->event), ev_struct->fd, ev_type, network_mysqld_con_handle, user_data); \ - chassis_event_add_with_timeout(srv, &(ev_struct->event), timeout); + return 0; +} - /** - * loop on the same connection as long as we don't end up in a stable state - */ - if (event_fd != -1) { - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::done"); - } else { - NETWORK_MYSQLD_CON_TRACK_TIME(con, "con_handle_start"); - } +/** + * Handle CON_STATE_INIT + */ +void +network_mysqld_con_state_init (int event_fd, network_mysqld_con *con) { - do { - struct timeval timeout; + chassis *srv = con->srv; + void *user_data = con; - ostate = con->state; -#ifdef NETWORK_DEBUG_TRACE_STATE_CHANGES - /* if you need the state-change information without dtrace, enable this */ - g_debug("%s: [%d] %s", - G_STRLOC, - getpid(), - network_mysqld_con_state_get_name(con->state)); -#endif + /* if we are a proxy ask the remote server for the hand-shake packet + * if not, we generate one */ + switch (plugin_call(srv, con, con->state)) { + case NETWORK_SOCKET_SUCCESS: + break; + default: + /** + * no luck, let's close the connection + */ + g_critical("%s.%d: plugin_call(CON_STATE_INIT) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); - MYSQLPROXY_STATE_CHANGE(event_fd, events, con->state); - switch (con->state) { - case CON_STATE_ERROR: - /* we can't go on, close the connection */ - { - gchar *which_connection = "a"; /* some connection, don't know yet */ - if (con->server && event_fd == con->server->fd) { - which_connection = "server"; - } else if (con->client && event_fd == con->client->fd) { - which_connection = "client"; - } - g_debug("[%s]: error on %s connection (fd: %d event: %d). closing client connection.", - G_STRLOC, which_connection, event_fd, events); - } - plugin_call_cleanup(srv, con); - network_mysqld_con_free(con); + con->state = CON_STATE_ERROR; - con = NULL; + break; + } - return; - case CON_STATE_CLOSE_CLIENT: - case CON_STATE_CLOSE_SERVER: - /* FIXME: this comment has nothing to do with reality... - * the server connection is still fine, - * let's keep it open for reuse */ +} - plugin_call_cleanup(srv, con); -#ifdef NETWORK_MYSQLD_WANT_CON_TRACK_TIME - /* dump the timestamps of this connection */ - if (srv->log->min_lvl == G_LOG_LEVEL_DEBUG) { - GList *node; - guint64 abs_usec = 0; - guint64 wait_event_usec = 0; - guint64 lua_usec = 0; - - for (node = con->timestamps->timestamps->head; node; node = node->next) { - chassis_timestamp_t *prev = node->prev ? node->prev->data : NULL; - chassis_timestamp_t *cur = node->data; - guint64 rel_usec = prev ? cur->usec - prev->usec: 0; - guint64 rel_cycles = prev ? cur->cycles - prev->cycles: 0; - - abs_usec += rel_usec; - - g_debug("%-35s usec=%8"G_GUINT64_FORMAT", cycles=%8"G_GUINT64_FORMAT", abs-usec=%8"G_GUINT64_FORMAT" (%s:%d)", - cur->name, - rel_usec, - rel_cycles, - abs_usec, - cur->filename, cur->line - ); - - if (strstr(cur->name, "leave_lua")) { - lua_usec += rel_usec; - } else if (strstr(cur->name, "wait_for_event::done")) { - wait_event_usec += rel_usec; - } - } +/** + * Handle CON_STATE_CLOSE_CLIENT | CON_STATE_CLOSE_SERVER + */ +void +network_mysqld_con_state_close (int event_fd, network_mysqld_con *con) { - g_debug("%-35s usec=%8"G_GUINT64_FORMAT"", - "abs wait-for-event::done", - wait_event_usec - ); - g_debug("%-35s usec=%8"G_GUINT64_FORMAT"", - "abs lua-exec::done", - lua_usec - ); + chassis *srv = con->srv; + void *user_data = con; + /* FIXME: this comment has nothing to do with reality... + * the server connection is still fine, + * let's keep it open for reuse */ + plugin_call_cleanup(srv, con); +#ifdef NETWORK_MYSQLD_WANT_CON_TRACK_TIME + /* dump the timestamps of this connection */ + if (srv->log->min_lvl == G_LOG_LEVEL_DEBUG) { + GList *node; + guint64 abs_usec = 0; + guint64 wait_event_usec = 0; + guint64 lua_usec = 0; + + for (node = con->timestamps->timestamps->head; node; node = node->next) { + chassis_timestamp_t *prev = node->prev ? node->prev->data : NULL; + chassis_timestamp_t *cur = node->data; + guint64 rel_usec = prev ? cur->usec - prev->usec: 0; + guint64 rel_cycles = prev ? cur->cycles - prev->cycles: 0; + + abs_usec += rel_usec; + + g_debug("%-35s usec=%8"G_GUINT64_FORMAT", cycles=%8"G_GUINT64_FORMAT", abs-usec=%8"G_GUINT64_FORMAT" (%s:%d)", + cur->name, + rel_usec, + rel_cycles, + abs_usec, + cur->filename, cur->line + ); + + if (strstr(cur->name, "leave_lua")) { + lua_usec += rel_usec; + } else if (strstr(cur->name, "wait_for_event::done")) { + wait_event_usec += rel_usec; } -#endif + } - network_mysqld_con_free(con); + g_debug("%-35s usec=%8"G_GUINT64_FORMAT"", + "abs wait-for-event::done", + wait_event_usec + ); + g_debug("%-35s usec=%8"G_GUINT64_FORMAT"", + "abs lua-exec::done", + lua_usec + ); - con = NULL; - return; - case CON_STATE_INIT: - /* if we are a proxy ask the remote server for the hand-shake packet - * if not, we generate one */ - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - default: - /** - * no luck, let's close the connection - */ - g_critical("%s.%d: plugin_call(CON_STATE_INIT) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); + } +#endif - con->state = CON_STATE_ERROR; - - break; - } + network_mysqld_con_free(con); - break; - case CON_STATE_CONNECT_SERVER: - switch ((retval = plugin_call(srv, con, con->state))) { - case NETWORK_SOCKET_SUCCESS: + con = NULL; +} - /** - * hmm, if this is success and we have something in the clients send-queue - * we just send it out ... who needs a server ? */ +/** + * Handle CON_STATE_ERROR + */ +void +network_mysqld_con_state_error (int event_fd, network_mysqld_con *con, short events) { - if ((con->client != NULL && con->client->send_queue->chunks->length > 0) && - con->server == NULL) { - /* we want to send something to the client */ + chassis *srv = con->srv; + void *user_data = con; - con->state = CON_STATE_SEND_HANDSHAKE; - } else { - g_assert(con->server); - } + /* we can't go on, close the connection */ + { + gchar *which_connection = "a"; /* some connection, don't know yet */ + if (con->server && event_fd == con->server->fd) { + which_connection = "server"; + } else if (con->client && event_fd == con->client->fd) { + which_connection = "client"; + } + g_debug("[%s]: error on %s connection (fd: %d event: %d). closing client connection.", + G_STRLOC, which_connection, event_fd, events); + } + plugin_call_cleanup(srv, con); + network_mysqld_con_free(con); - break; - case NETWORK_SOCKET_ERROR_RETRY: - if (con->server) { - timeout = con->connect_timeout; - /** - * we have a server connection waiting to begin writable - */ - WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::connect_server"); - return; - } else { - /* try to get a connection to another backend, - * - * setting ostate = CON_STATE_INIT is a hack to make sure - * the loop is coming back to this function again */ - ostate = CON_STATE_INIT; - } + con = NULL; +} - break; - case NETWORK_SOCKET_ERROR: - /** - * connecting failed and no option to retry - * - * close the connection - */ - con->state = CON_STATE_SEND_ERROR; - break; - default: - g_critical("%s: hook for CON_STATE_CONNECT_SERVER return invalid return code: %d", - G_STRLOC, - retval); +/** + * Handle read event on connection + */ +void +network_mysqld_con_handle_evread (int event_fd, network_mysqld_con *con) { - con->state = CON_STATE_ERROR; - - break; - } + int b = -1; - break; - case CON_STATE_READ_HANDSHAKE: { - /** - * read auth data from the remote mysql-server - */ - network_socket *recv_sock; - recv_sock = con->server; - g_assert(events == 0 || event_fd == recv_sock->fd); + /** + * check how much data there is to read + * + * ioctl() + * - returns 0 if connection is closed + * - or -1 and ECONNRESET on solaris + * or -1 and EPIPE on HP/UX + */ + if (ioctl(event_fd, FIONREAD, &b)) { + switch (errno) { + case E_NET_CONNRESET: /* solaris */ + case EPIPE: /* hp/ux */ + if (con->client && event_fd == con->client->fd) { + /* the client closed the connection, let's keep the server side open */ + con->state = CON_STATE_CLOSE_CLIENT; + } else if (con->server && event_fd == con->server->fd && con->com_quit_seen) { + con->state = CON_STATE_CLOSE_SERVER; + } else { + /* server side closed on use, oops, close both sides */ + con->state = CON_STATE_ERROR; + } + break; + default: + g_critical("ioctl(%d, FIONREAD, ...) failed: %s", event_fd, g_strerror(errno)); + + con->state = CON_STATE_ERROR; + break; + } + } else if (b != 0) { + if (con->client && event_fd == con->client->fd) { + con->client->to_read = b; + } else if (con->server && event_fd == con->server->fd) { + con->server->to_read = b; + } else { + g_error("%s.%d: neither nor", __FILE__, __LINE__); + } + } else { /* Linux */ + if (con->client && event_fd == con->client->fd) { + /* the client closed the connection, let's keep the server side open */ + con->state = CON_STATE_CLOSE_CLIENT; + } else if (con->server && event_fd == con->server->fd && con->com_quit_seen) { + con->state = CON_STATE_CLOSE_SERVER; + } else { + /* server side closed on use, oops, close both sides */ + con->state = CON_STATE_ERROR; + } + } - switch (network_mysqld_read(srv, recv_sock)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; +} - /* call us again when you have a event */ - WAIT_FOR_EVENT(con->server, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_handshake"); +/** + * call the cleanup callback for the current connection + * + * @param srv global context + * @param con connection context + * + * @return NETWORK_SOCKET_SUCCESS on success + */ +network_socket_retval_t plugin_call_cleanup(chassis *srv, network_mysqld_con *con) { + NETWORK_MYSQLD_PLUGIN_FUNC(func) = NULL; + network_socket_retval_t retval = NETWORK_SOCKET_SUCCESS; - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_HANDSHAKE) returned an error", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } + func = con->plugins.con_cleanup; + + if (!func) return retval; - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + LOCK_LUA(srv->priv->sc); + retval = (*func)(srv, con); + UNLOCK_LUA(srv->priv->sc); - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_ERROR: - /** - * we couldn't understand the pack from the server - * - * we have something in the queue and will send it to the client - * and close the connection afterwards - */ - - con->state = CON_STATE_SEND_ERROR; + return retval; +} - break; - default: - g_critical("%s.%d: ...", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } +/** + * call the timeout callback for the current connection + * + * @param srv global context + * @param con connection context + * + * @return NETWORK_SOCKET_SUCCESS on success + */ +static network_socket_retval_t +plugin_call_timeout(chassis *srv, network_mysqld_con *con) { + NETWORK_MYSQLD_PLUGIN_FUNC(func) = NULL; + network_socket_retval_t retval = NETWORK_SOCKET_ERROR; + + func = con->plugins.con_timeout; - break; } - case CON_STATE_SEND_HANDSHAKE: - /* send the hand-shake to the client and wait for a response */ + if (!func) { + /* default implementation */ + g_debug("%s: connection between %s and %s timed out. closing it", + G_STRLOC, + con->client ? con->client->src->name->str : "(client)", + con->server ? con->server->dst->name->str : "(server)"); + con->state = CON_STATE_ERROR; + return NETWORK_SOCKET_SUCCESS; + } - switch (network_mysqld_write(srv, con->client)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; + LOCK_LUA(srv->priv->sc); + retval = (*func)(srv, con); + UNLOCK_LUA(srv->priv->sc); - WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_handshake"); - - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - /** - * writing failed, closing connection - */ - con->state = CON_STATE_ERROR; - break; - } + return retval; +} - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - default: - g_critical("%s.%d: plugin_call(CON_STATE_SEND_HANDSHAKE) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } +chassis_private *network_mysqld_priv_init(void) { + chassis_private *priv; - break; - case CON_STATE_READ_AUTH: { - /* read auth from client */ - network_socket *recv_sock; + priv = g_new0(chassis_private, 1); - recv_sock = con->client; + priv->cons = g_ptr_array_new(); + priv->sc = lua_scope_new(); + priv->backends = network_backends_new(); - g_assert(events == 0 || event_fd == recv_sock->fd); + return priv; +} - switch (network_mysqld_read(srv, recv_sock)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; +void network_mysqld_priv_shutdown(chassis *chas, chassis_private *priv) { + if (!priv) return; - WAIT_FOR_EVENT(con->client, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_auth"); + /* network_mysqld_con_free() changes the priv->cons directly + * + * always free the first element until all are gone + */ + while (0 != priv->cons->len) { + network_mysqld_con *con = priv->cons->pdata[0]; - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_AUTH) returned an error", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + plugin_call_cleanup(chas, con); + network_mysqld_con_free(con); + } +} - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_ERROR: - con->state = CON_STATE_SEND_ERROR; - break; - default: - g_critical("%s.%d: plugin_call(CON_STATE_READ_AUTH) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } +void network_mysqld_priv_free(chassis G_GNUC_UNUSED *chas, chassis_private *priv) { + if (!priv) return; - break; } - case CON_STATE_SEND_AUTH: - /* send the auth-response to the server */ - switch (network_mysqld_write(srv, con->server)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; + g_ptr_array_free(priv->cons, TRUE); - WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_auth"); + network_backends_free(priv->backends); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - /* might be a connection close, we should just close the connection and be happy */ - con->state = CON_STATE_ERROR; + lua_scope_free(priv->sc); - break; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + g_free(priv); +} - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - default: - g_critical("%s.%d: plugin_call(CON_STATE_SEND_AUTH) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } +int network_mysqld_init(chassis *srv) { + lua_State *L; + srv->priv_free = network_mysqld_priv_free; + srv->priv_shutdown = network_mysqld_priv_shutdown; + srv->priv = network_mysqld_priv_init(); - break; - case CON_STATE_READ_AUTH_RESULT: { - /* read the auth result from the server */ - network_socket *recv_sock; + /* store the pointer to the chassis in the Lua registry */ + L = srv->priv->sc->L; + lua_pushlightuserdata(L, (void*)srv); + lua_setfield(L, LUA_REGISTRYINDEX, CHASSIS_LUA_REGISTRY_KEY); + + return 0; +} - recv_sock = con->server; - g_assert(events == 0 || event_fd == recv_sock->fd); +network_mysqld_con *network_mysqld_con_init() { + return network_mysqld_con_new(); +} +/** + * create a connection + * + * @return a connection context + */ +network_mysqld_con *network_mysqld_con_new() { + network_mysqld_con *con; - switch (network_mysqld_read(srv, recv_sock)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; + con = g_new0(network_mysqld_con, 1); + con->timestamps = chassis_timestamps_new(); + con->parse.command = -1; + con->auth_switch_to_method = g_string_new(NULL); + con->auth_switch_to_round = 0; + con->auth_switch_to_data = g_string_new(NULL);; - WAIT_FOR_EVENT(con->server, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_auth_result"); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_AUTH_RESULT) returned an error", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + /* some tiny helper macros */ +#define SECONDS ( 1 ) +#define MINUTES ( 60 * SECONDS ) +#define HOURS ( 60 * MINUTES ) + con->connect_timeout.tv_sec = 2 * SECONDS; + con->connect_timeout.tv_usec = 0; - if (0 != network_mysqld_con_track_auth_result_state(con)) { - con->state = CON_STATE_ERROR; - break; - } + con->read_timeout.tv_sec = 8 * HOURS; + con->read_timeout.tv_usec = 0; + + con->write_timeout.tv_sec = 8 * HOURS; + con->write_timeout.tv_usec = 0; +#undef SECONDS +#undef MINUTES +#undef HOURS - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - default: - g_critical("%s.%d: plugin_call(CON_STATE_READ_AUTH_RESULT) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); + return con; +} - con->state = CON_STATE_ERROR; - break; - } +void network_mysqld_add_connection(chassis *srv, network_mysqld_con *con) { + con->srv = srv; - break; } - case CON_STATE_SEND_AUTH_RESULT: { - /* send the hand-shake to the client and wait for a response */ + g_ptr_array_add(srv->priv->cons, con); +} - switch (network_mysqld_write(srv, con->client)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; +/** + * free a connection + * + * closes the client and server sockets + * + * @param con connection context + */ +void network_mysqld_con_free(network_mysqld_con *con) { + if (!con) return; - WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_auth_result"); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_debug("%s.%d: network_mysqld_write(CON_STATE_SEND_AUTH_RESULT) returned an error", __FILE__, __LINE__); + if (con->parse.data && con->parse.data_free) { + con->parse.data_free(con->parse.data); + } - con->state = CON_STATE_ERROR; - break; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + if (con->server) network_socket_free(con->server); + if (con->client) network_socket_free(con->client); - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - default: - g_critical("%s.%d: ...", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } - - break; } - case CON_STATE_READ_AUTH_OLD_PASSWORD: - /* read auth from client */ - switch (network_mysqld_read(srv, con->client)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; + g_string_free(con->auth_switch_to_method, TRUE); + g_string_free(con->auth_switch_to_data, TRUE); - WAIT_FOR_EVENT(con->client, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_auth_old_password"); + /* we are still in the conns-array */ - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_AUTH_OLD_PASSWORD) returned an error", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - return; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + g_ptr_array_remove_fast(con->srv->priv->cons, con); + chassis_timestamps_free(con->timestamps); - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - default: - g_critical("%s.%d: plugin_call(CON_STATE_READ_AUTH_OLD_PASSWORD) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } + g_free(con); +} - break; - case CON_STATE_SEND_AUTH_OLD_PASSWORD: - /* send the auth-response to the server */ - switch (network_mysqld_write(srv, con->server)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; +#if 0 +static void dump_str(const char *msg, const unsigned char *s, size_t len) { + GString *hex; + size_t i; + + hex = g_string_new(NULL); - WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_auth_old_password"); + for (i = 0; i < len; i++) { + g_string_append_printf(hex, "%02x", s[i]); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - /* might be a connection close, we should just close the connection and be happy */ - g_debug("%s.%d: network_mysqld_write(CON_STATE_SEND_AUTH_OLD_PASSWORD) returned an error", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + if ((i + 1) % 16 == 0) { + g_string_append(hex, "\n"); + } else { + g_string_append_c(hex, ' '); + } - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - break; - default: - g_critical("%s.%d: plugin_call(CON_STATE_SEND_AUTH_OLD_PASSWORD) != NETWORK_SOCKET_SUCCESS", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } + } - break; + g_message("(%s): %s", msg, hex->str); - case CON_STATE_READ_QUERY: { - network_socket *recv_sock; - network_packet last_packet; + g_string_free(hex, TRUE); +} +#endif - recv_sock = con->client; +int network_mysqld_queue_reset(network_socket *sock) { + printf("QUEUE_RESET (%p) => %s\n", sock, sock->dst->name->str); + sock->packet_id_is_reset = TRUE; - g_assert(events == 0 || event_fd == recv_sock->fd); + return 0; +} - do { - switch (network_mysqld_read(srv, recv_sock)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; +/** + * synchronize the packet-ids of two network-sockets + */ +int network_mysqld_queue_sync(network_socket *dst, network_socket *src) { + g_assert_cmpint(src->packet_id_is_reset, ==, FALSE); - WAIT_FOR_EVENT(con->client, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_query"); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_QUERY) returned an error", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - return; - } - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + if (dst->packet_id_is_reset == FALSE) { + /* this shouldn't really happen */ + } - last_packet.data = g_queue_peek_tail(recv_sock->recv_queue->chunks); - } while (last_packet.data->len == PACKET_LEN_MAX + NET_HEADER_SIZE); /* read all chunks of the overlong data */ + dst->last_packet_id = src->last_packet_id - 1; - if (con->server && - con->server->challenge && - con->server->challenge->server_version > 50113 && con->server->challenge->server_version < 50118) { - /** - * Bug #25371 - * - * COM_CHANGE_USER returns 2 ERR packets instead of one - * - * we can auto-correct the issue if needed and remove the second packet - * Some clients handle this issue and expect a double ERR packet. + return 0; +} + +/** + * appends a raw MySQL packet to the queue + * + * the packet is append the queue directly and shouldn't be used by the caller afterwards anymore + * and has to by in the MySQL Packet format + * + */ +int network_mysqld_queue_append_raw(network_socket *sock, network_queue *queue, GString *data) { + guint32 packet_len; + guint8 packet_id; + + /* check that the length header is valid */ + if (queue != sock->send_queue && + queue != sock->recv_queue) { + g_critical("%s: queue = %p doesn't belong to sock %p", + G_STRLOC, + (void *)queue, + (void *)sock); + return -1; + } + + g_assert_cmpint(data->len, >=, 4); + + packet_len = network_mysqld_proto_get_packet_len(data); + packet_id = network_mysqld_proto_get_packet_id(data); + + g_assert_cmpint(packet_len, ==, data->len - 4); + + if (sock->packet_id_is_reset) { + /* the ->last_packet_id is undefined, accept what we get */ + sock->last_packet_id = packet_id; + sock->packet_id_is_reset = FALSE; + } else if (packet_id != (guint8)(sock->last_packet_id + 1)) { + sock->last_packet_id++; +#if 0 + g_critical("%s: packet-id %d doesn't match for socket's last packet %d, patching it", + G_STRLOC, + packet_id, + sock->last_packet_id); +#endif + network_mysqld_proto_set_packet_id(data, sock->last_packet_id); + } else { + sock->last_packet_id++; + } + + network_queue_append(queue, data); + + return 0; +} + +/** + * appends a payload to the queue + * + * the packet is copied and prepened with the mysql packet header before it is appended to the queue + * if neccesary the payload is spread over multiple mysql packets + */ +int network_mysqld_queue_append(network_socket *sock, network_queue *queue, const char *data, size_t packet_len) { + gsize packet_offset = 0; + + do { + GString *s; + gsize cur_packet_len = MIN(packet_len, PACKET_LEN_MAX); + + s = g_string_sized_new(packet_len + 4); + + if (sock->packet_id_is_reset) { + sock->packet_id_is_reset = FALSE; + sock->last_packet_id = 0xff; /** the ++last_packet_id will make sure we send a 0 */ + } + + network_mysqld_proto_append_packet_len(s, cur_packet_len); + network_mysqld_proto_append_packet_id(s, ++sock->last_packet_id); + g_string_append_len(s, data + packet_offset, cur_packet_len); + + network_queue_append(queue, s); + + if (packet_len == PACKET_LEN_MAX) { + s = g_string_sized_new(4); + + network_mysqld_proto_append_packet_len(s, 0); + network_mysqld_proto_append_packet_id(s, ++sock->last_packet_id); + + network_queue_append(queue, s); + } + + packet_len -= cur_packet_len; + packet_offset += cur_packet_len; + } while (packet_len > 0); + + return 0; +} + + +/** + * create a OK packet and append it to the send-queue + * + * @param con a client socket + * @param affected_rows affected rows + * @param insert_id insert_id + * @param server_status server_status (bitfield of SERVER_STATUS_*) + * @param warnings number of warnings to fetch with SHOW WARNINGS + * @return 0 + * + * @todo move to network_mysqld_proto + */ +int network_mysqld_con_send_ok_full(network_socket *con, guint64 affected_rows, guint64 insert_id, guint16 server_status, guint16 warnings ) { + GString *packet = g_string_new(NULL); + network_mysqld_ok_packet_t *ok_packet; + + ok_packet = network_mysqld_ok_packet_new(); + ok_packet->affected_rows = affected_rows; + ok_packet->insert_id = insert_id; + ok_packet->server_status = server_status; + ok_packet->warnings = warnings; + + network_mysqld_proto_append_ok_packet(packet, ok_packet); + + network_mysqld_queue_append(con, con->send_queue, S(packet)); + network_mysqld_queue_reset(con); + + g_string_free(packet, TRUE); + network_mysqld_ok_packet_free(ok_packet); + + return 0; +} + +/** + * send a simple OK packet + * + * - no affected rows + * - no insert-id + * - AUTOCOMMIT + * - no warnings + * + * @param con a client socket + */ +int network_mysqld_con_send_ok(network_socket *con) { + return network_mysqld_con_send_ok_full(con, 0, 0, SERVER_STATUS_AUTOCOMMIT, 0); +} + +static int network_mysqld_con_send_error_full_all(network_socket *con, + const char *errmsg, gsize errmsg_len, + guint errorcode, + const gchar *sqlstate, + gboolean is_41_protocol) { + GString *packet; + network_mysqld_err_packet_t *err_packet; + + packet = g_string_sized_new(10 + errmsg_len); + + err_packet = is_41_protocol ? network_mysqld_err_packet_new() : network_mysqld_err_packet_new_pre41(); + err_packet->errcode = errorcode; + if (errmsg) g_string_assign_len(err_packet->errmsg, errmsg, errmsg_len); + if (sqlstate) g_string_assign_len(err_packet->sqlstate, sqlstate, strlen(sqlstate)); + + network_mysqld_proto_append_err_packet(packet, err_packet); + + network_mysqld_queue_append(con, con->send_queue, S(packet)); + network_mysqld_queue_reset(con); + + network_mysqld_err_packet_free(err_packet); + g_string_free(packet, TRUE); + + return 0; +} + +/** + * send a error packet to the client connection + * + * @note the sqlstate has to match the SQL standard. If no matching SQL state is known, leave it at NULL + * + * @param con the client connection + * @param errmsg the error message + * @param errmsg_len byte-len of the error-message + * @param errorcode mysql error-code we want to send + * @param sqlstate if none-NULL, 5-char SQL state to send, if NULL, default SQL state is used + * + * @return 0 on success + */ +int network_mysqld_con_send_error_full(network_socket *con, const char *errmsg, gsize errmsg_len, guint errorcode, const gchar *sqlstate) { + return network_mysqld_con_send_error_full_all(con, errmsg, errmsg_len, errorcode, sqlstate, TRUE); +} + + +/** + * send a error-packet to the client connection + * + * errorcode is 1000, sqlstate is NULL + * + * @param con the client connection + * @param errmsg the error message + * @param errmsg_len byte-len of the error-message + * + * @see network_mysqld_con_send_error_full + */ +int network_mysqld_con_send_error(network_socket *con, const char *errmsg, gsize errmsg_len) { + return network_mysqld_con_send_error_full(con, errmsg, errmsg_len, ER_UNKNOWN_ERROR, NULL); +} + +/** + * send a error packet to the client connection (pre-4.1 protocol) + * + * @param con the client connection + * @param errmsg the error message + * @param errmsg_len byte-len of the error-message + * @param errorcode mysql error-code we want to send + * + * @return 0 on success + */ +int network_mysqld_con_send_error_pre41_full(network_socket *con, const char *errmsg, gsize errmsg_len, guint errorcode) { + return network_mysqld_con_send_error_full_all(con, errmsg, errmsg_len, errorcode, NULL, FALSE); +} + +/** + * send a error-packet to the client connection (pre-4.1 protocol) + * + * @param con the client connection + * @param errmsg the error message + * @param errmsg_len byte-len of the error-message + * + * @see network_mysqld_con_send_error_pre41_full + */ +int network_mysqld_con_send_error_pre41(network_socket *con, const char *errmsg, gsize errmsg_len) { + return network_mysqld_con_send_error_pre41_full(con, errmsg, errmsg_len, ER_UNKNOWN_ERROR); +} + + +/** + * get a full packet from the raw queue and move it to the packet queue + */ +network_socket_retval_t network_mysqld_con_get_packet(chassis G_GNUC_UNUSED*chas, network_socket *con) { + GString *packet = NULL; + GString header; + char header_str[NET_HEADER_SIZE + 1] = ""; + guint32 packet_len; + guint8 packet_id; + + /** + * read the packet header (4 bytes) + */ + header.str = header_str; + header.allocated_len = sizeof(header_str); + header.len = 0; + + /* read the packet len if the leading packet */ + if (!network_queue_peek_string(con->recv_queue_raw, NET_HEADER_SIZE, &header)) { + /* too small */ + + return NETWORK_SOCKET_WAIT_FOR_EVENT; + } + + packet_len = network_mysqld_proto_get_packet_len(&header); + packet_id = network_mysqld_proto_get_packet_id(&header); + + /* move the packet from the raw queue to the recv-queue */ + if ((packet = network_queue_pop_string(con->recv_queue_raw, packet_len + NET_HEADER_SIZE, NULL))) { +#ifdef NETWORK_DEBUG_TRACE_IO + /* to trace the data we received from the socket, enable this */ + g_debug_hexdump(G_STRLOC, S(packet)); +#endif + + if (con->packet_id_is_reset) { + printf("RESET ===> Packet id %d => Last packet id %d\n", packet_id, con->last_packet_id); + con->last_packet_id = packet_id; + con->packet_id_is_reset = FALSE; + } else if (packet_id != (guint8)(con->last_packet_id + 1)) { + g_critical("%s: received packet-id %d, but expected %d ... out of sync.", + G_STRLOC, + packet_id, + con->last_packet_id + 1); + /*return NETWORK_SOCKET_ERROR;*/ + con->last_packet_id = ++packet_id; + /*network_mysqld_proto_set_packet_id(data, sock->last_packet_id);*/ + } else { + printf("UPDATE ===> Packet id %d => Last packet id %d\n", packet_id, con->last_packet_id); + con->last_packet_id = packet_id; + } + + network_queue_append(con->recv_queue, packet); + } else { + return NETWORK_SOCKET_WAIT_FOR_EVENT; + } + + return NETWORK_SOCKET_SUCCESS; +} + +/** + * read a MySQL packet from the socket + * + * the packet is added to the con->recv_queue and contains a full mysql packet + * with packet-header and everything + */ +network_socket_retval_t network_mysqld_read(chassis G_GNUC_UNUSED*chas, network_socket *con) { + switch (network_socket_read(con)) { + case NETWORK_SOCKET_WAIT_FOR_EVENT: + return NETWORK_SOCKET_WAIT_FOR_EVENT; + case NETWORK_SOCKET_ERROR: + return NETWORK_SOCKET_ERROR; + case NETWORK_SOCKET_SUCCESS: + break; + case NETWORK_SOCKET_ERROR_RETRY: + g_error("NETWORK_SOCKET_ERROR_RETRY wasn't expected"); + break; + } + + return network_mysqld_con_get_packet(chas, con); +} + +network_socket_retval_t network_mysqld_write(chassis G_GNUC_UNUSED*chas, network_socket *con) { + network_socket_retval_t ret; + + ret = network_socket_write(con, -1); + + return ret; +} + +/** + * call the hooks of the plugins for each state + * + * if the plugin doesn't implement a hook, we provide a default operation + * + * @param srv the global context + * @param con the connection context + * @param state state to handle + * @return NETWORK_SOCKET_SUCCESS on success + */ +network_socket_retval_t plugin_call(chassis *srv, network_mysqld_con *con, int state) { + network_socket_retval_t ret; + NETWORK_MYSQLD_PLUGIN_FUNC(func) = NULL; + + printf("STATE = %s\n", network_mysqld_con_state_get_name(state)); + + switch (state) { + case CON_STATE_INIT: + func = con->plugins.con_init; + + if (!func) { /* default implementation */ + con->state = CON_STATE_CONNECT_SERVER; + } + break; + case CON_STATE_CONNECT_SERVER: + func = con->plugins.con_connect_server; + + if (!func) { /* default implementation */ + con->state = CON_STATE_READ_HANDSHAKE; + } + + break; + case CON_STATE_SEND_HANDSHAKE: + func = con->plugins.con_send_handshake; + + if (!func) { /* default implementation */ + con->state = CON_STATE_READ_AUTH; + } + + break; + case CON_STATE_READ_HANDSHAKE: + func = con->plugins.con_read_handshake; + + break; + case CON_STATE_READ_AUTH: + func = con->plugins.con_read_auth; + + break; + case CON_STATE_SEND_AUTH: + func = con->plugins.con_send_auth; + + if (!func) { /* default implementation */ + con->state = CON_STATE_READ_AUTH_RESULT; + } + break; + case CON_STATE_READ_AUTH_RESULT: + func = con->plugins.con_read_auth_result; + break; + case CON_STATE_SEND_AUTH_RESULT: + /* called after the auth data is sent to the client */ + func = con->plugins.con_send_auth_result; + + if (!func) { + /* + * figure out what to do next: + * - switch to 'read command from client' + * - close connection + * - read auth-data from client + * - read another auth-result packet from server + */ + switch (con->auth_result_state) { + case MYSQLD_PACKET_OK: + /* OK, delivered to client, switch to command phase */ + con->state = CON_STATE_READ_QUERY; + break; + case MYSQLD_PACKET_ERR: + /* ERR delivered to client, close the connection now */ + con->state = CON_STATE_ERROR; + break; + case 0x01: /* more auth data */ + /** + * FIXME: we should track that the server only sends us a 0x01 reply if + * we first went through "switch auth packet" */ - network_packet packet; - guint8 com; - packet.data = g_queue_peek_head(recv_sock->recv_queue->chunks); - packet.offset = 0; - - if (0 == network_mysqld_proto_skip_network_header(&packet) && - 0 == network_mysqld_proto_get_int8(&packet, &com) && - com == COM_CHANGE_USER) { - network_mysqld_con_send_error(con->client, C("COM_CHANGE_USER is broken on 5.1.14-.17, please upgrade the MySQL Server")); - con->state = CON_STATE_SEND_QUERY_RESULT; + /** + * if we switched to win-auth and SPNEGO is used, check if the response packet contains: + * + * negState = accept-succeeded. + */ + if ((strleq(S(con->auth_switch_to_method), C("authentication_windows_client"))) && + con->auth_next_packet_is_from_server) { + /* we either have SPNEGO or NTLM */ + con->state = CON_STATE_READ_AUTH_RESULT; break; } - } - - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: + case MYSQLD_PACKET_EOF: + /* + * next, read the auth data from the client + */ + con->state = CON_STATE_READ_AUTH_OLD_PASSWORD; break; default: - g_critical("%s.%d: plugin_call(CON_STATE_READ_QUERY) failed", __FILE__, __LINE__); - + g_debug("%s.%d: unexpected state for SEND_AUTH_RESULT: %02x", + __FILE__, __LINE__, + con->auth_result_state); con->state = CON_STATE_ERROR; break; } + } + break; + case CON_STATE_READ_AUTH_OLD_PASSWORD: + func = con->plugins.con_read_auth_old_password; - /** - * there should be 3 possible next states from here: - * - * - CON_STATE_ERROR (if something went wrong and we want to close the connection - * - CON_STATE_SEND_QUERY (if we want to send data to the con->server) - * - CON_STATE_SEND_QUERY_RESULT (if we want to send data to the con->client) - * - * @todo verify this with a clean switch () - */ + if (!func) { + network_socket *recv_sock, *send_sock; + network_packet packet; + guint32 packet_len; - /* reset the tracked command - * - * if the plugin decided to send a result, it has to track the commands itself - * otherwise LOAD DATA LOCAL INFILE and friends will fail - */ - if (con->state == CON_STATE_SEND_QUERY) { - network_mysqld_con_reset_command_response_state(con); - } + /* move the packet to the send queue */ - break; } - case CON_STATE_SEND_QUERY: - /* send the query to the server - * - * this state will loop until all the packets from the send-queue are flushed - */ + recv_sock = con->client; + send_sock = con->server; - if (con->server->send_queue->offset == 0) { - /* only parse the packets once */ - network_packet packet; - - packet.data = g_queue_peek_head(con->server->send_queue->chunks); - packet.offset = 0; - - if (0 != network_mysqld_con_command_states_init(con, &packet)) { - g_debug("%s: tracking mysql protocol states failed", - G_STRLOC); - con->state = CON_STATE_ERROR; + if (NULL == con->server) { + /** + * we have to auth against same backend as we did before + * but the user changed it + */ - break; - } - } - - switch (network_mysqld_write(srv, con->server)) { - case NETWORK_SOCKET_SUCCESS: + g_message("%s.%d: (lua) read-auth-old-password failed as backend_ndx got reset.", __FILE__, __LINE__); + + network_mysqld_con_send_error(con->client, C("(lua) read-auth-old-password failed as backend_ndx got reset.")); + con->state = CON_STATE_SEND_ERROR; break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; + } - WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_query"); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_debug("%s.%d: network_mysqld_write(CON_STATE_SEND_QUERY) returned an error", __FILE__, __LINE__); + packet.data = g_queue_peek_head(recv_sock->recv_queue->chunks); + packet.offset = 0; + packet_len = network_mysqld_proto_get_packet_len(packet.data); + + if ((strleq(S(con->auth_switch_to_method), C("authentication_windows_client"))) && + (con->auth_switch_to_round == 0) && + (packet_len == 255)) { +#if 1 /** - * write() failed, close the connections + * FIXME: the 2-packet win-auth protocol enhancements aren't properly tested yet. + * therefore they are disabled for now. */ - con->state = CON_STATE_ERROR; - break; + g_string_free(g_queue_pop_head(recv_sock->recv_queue->chunks), TRUE); + + network_mysqld_con_send_error(recv_sock, C("long packets for windows-authentication aren't completely handled yet. Please use another auth-method for now.")); + + con->state = CON_STATE_SEND_ERROR; +#else + con->auth_switch_to_round++; + /* move the packet to the send-queue + */ + network_mysqld_queue_append_raw(send_sock, send_sock->send_queue, + g_queue_pop_head(recv_sock->recv_queue->chunks)); + + /* stay in this state and read the next packet too */ +#endif + } else { + /* move the packet to the send-queue + */ + network_mysqld_queue_append_raw(send_sock, send_sock->send_queue, + g_queue_pop_head(recv_sock->recv_queue->chunks)); + + con->state = CON_STATE_SEND_AUTH_OLD_PASSWORD; } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + } + break; + case CON_STATE_SEND_AUTH_OLD_PASSWORD: + /** + * data is at the server, read the response next + */ + con->state = CON_STATE_READ_AUTH_RESULT; + break; + case CON_STATE_READ_QUERY: + func = con->plugins.con_read_query; + break; + case CON_STATE_READ_QUERY_RESULT: + func = con->plugins.con_read_query_result; + break; + case CON_STATE_SEND_QUERY_RESULT: + func = con->plugins.con_send_query_result; + + if (!func) { /* default implementation */ + con->state = CON_STATE_READ_QUERY; + } + break; + + case CON_STATE_SEND_LOCAL_INFILE_DATA: + func = con->plugins.con_send_local_infile_data; + + if (!func) { /* default implementation */ + con->state = CON_STATE_READ_LOCAL_INFILE_RESULT; + } + + break; + case CON_STATE_READ_LOCAL_INFILE_DATA: + func = con->plugins.con_read_local_infile_data; + + if (!func) { /* the plugins have to implement this function to track LOAD DATA LOCAL INFILE handling work */ + con->state = CON_STATE_ERROR; + } + + break; + case CON_STATE_SEND_LOCAL_INFILE_RESULT: + func = con->plugins.con_send_local_infile_result; + + if (!func) { /* default implementation */ + con->state = CON_STATE_READ_QUERY; + } + + break; + case CON_STATE_READ_LOCAL_INFILE_RESULT: + func = con->plugins.con_read_local_infile_result; + + if (!func) { /* the plugins have to implement this function to track LOAD DATA LOCAL INFILE handling work */ + con->state = CON_STATE_ERROR; + } + + break; + case CON_STATE_ERROR: + g_debug("%s.%d: not executing plugin function in state CON_STATE_ERROR", __FILE__, __LINE__); + return NETWORK_SOCKET_SUCCESS; + default: + g_error("%s.%d: unhandled state: %d", + __FILE__, __LINE__, + state); + } + if (!func) return NETWORK_SOCKET_SUCCESS; + + LOCK_LUA(srv->priv->sc); + ret = (*func)(srv, con); + UNLOCK_LUA(srv->priv->sc); + + return ret; +} + +/** + * reset the command-response parsing + * + * some commands needs state information and we have to + * reset the parsing as soon as we add a new command to the send-queue + */ +void network_mysqld_con_reset_command_response_state(network_mysqld_con *con) { + con->parse.command = -1; + if (con->parse.data && con->parse.data_free) { + con->parse.data_free(con->parse.data); + + con->parse.data = NULL; + con->parse.data_free = NULL; + } +} + +/** + * get the name of a connection state + */ +const char *network_mysqld_con_state_get_name(network_mysqld_con_state_t state) { + switch (state) { + case CON_STATE_INIT: return "CON_STATE_INIT"; + case CON_STATE_CONNECT_SERVER: return "CON_STATE_CONNECT_SERVER"; + case CON_STATE_READ_HANDSHAKE: return "CON_STATE_READ_HANDSHAKE"; + case CON_STATE_SEND_HANDSHAKE: return "CON_STATE_SEND_HANDSHAKE"; + case CON_STATE_READ_AUTH: return "CON_STATE_READ_AUTH"; + case CON_STATE_SEND_AUTH: return "CON_STATE_SEND_AUTH"; + case CON_STATE_READ_AUTH_OLD_PASSWORD: return "CON_STATE_READ_AUTH_OLD_PASSWORD"; + case CON_STATE_SEND_AUTH_OLD_PASSWORD: return "CON_STATE_SEND_AUTH_OLD_PASSWORD"; + case CON_STATE_READ_AUTH_RESULT: return "CON_STATE_READ_AUTH_RESULT"; + case CON_STATE_SEND_AUTH_RESULT: return "CON_STATE_SEND_AUTH_RESULT"; + case CON_STATE_READ_QUERY: return "CON_STATE_READ_QUERY"; + case CON_STATE_SEND_QUERY: return "CON_STATE_SEND_QUERY"; + case CON_STATE_READ_QUERY_RESULT: return "CON_STATE_READ_QUERY_RESULT"; + case CON_STATE_SEND_QUERY_RESULT: return "CON_STATE_SEND_QUERY_RESULT"; + case CON_STATE_READ_LOCAL_INFILE_DATA: return "CON_STATE_READ_LOCAL_INFILE_DATA"; + case CON_STATE_SEND_LOCAL_INFILE_DATA: return "CON_STATE_SEND_LOCAL_INFILE_DATA"; + case CON_STATE_READ_LOCAL_INFILE_RESULT: return "CON_STATE_READ_LOCAL_INFILE_RESULT"; + case CON_STATE_SEND_LOCAL_INFILE_RESULT: return "CON_STATE_SEND_LOCAL_INFILE_RESULT"; + case CON_STATE_CLOSE_CLIENT: return "CON_STATE_CLOSE_CLIENT"; + case CON_STATE_CLOSE_SERVER: return "CON_STATE_CLOSE_SERVER"; + case CON_STATE_ERROR: return "CON_STATE_ERROR"; + case CON_STATE_SEND_ERROR: return "CON_STATE_SEND_ERROR"; + } + + return "unknown"; +} + +static int network_mysqld_con_track_auth_result_state(network_mysqld_con *con) { + network_packet packet; + guint8 state; + int err = 0; + + /** + * depending on the result-set we have different exit-points + * - OK -> READ_QUERY + * - EOF -> (read old password hash) + * - ERR -> ERROR + */ + packet.data = g_queue_peek_head(con->server->recv_queue->chunks); + packet.offset = 0; + + err = err || network_mysqld_proto_skip_network_header(&packet); + err = err || network_mysqld_proto_peek_int8(&packet, &state); + + if (err) return -1; + + con->auth_result_state = state; + + if (state == 0xfe) { + /* a long auth-switch packet */ + err = err || network_mysqld_proto_skip(&packet, 1); + + if (packet.data->len - packet.offset > 0) { + err = err || network_mysqld_proto_get_gstring(&packet, con->auth_switch_to_method); + err = err || network_mysqld_proto_get_gstring_len(&packet, packet.data->len - packet.offset, con->auth_switch_to_data); + } else { + /* just in case we get here switch ... which shouldn't happen ... */ + g_string_truncate(con->auth_switch_to_method, 0); + g_string_truncate(con->auth_switch_to_data, 0); + } + con->auth_switch_to_round = 0; + con->auth_next_packet_is_from_server = FALSE; + } else if (state == 0x01) { + if ((strleq(S(con->auth_switch_to_method), C("authentication_windows_client")))) { + GError *gerr = NULL; - /* some statements don't have a server response */ - switch (con->parse.command) { - case COM_STMT_SEND_LONG_DATA: /* not acked */ - case COM_STMT_CLOSE: - con->state = CON_STATE_READ_QUERY; - if (con->client) network_mysqld_queue_reset(con->client); - if (con->server) network_mysqld_queue_reset(con->server); - break; - default: - con->state = CON_STATE_READ_QUERY_RESULT; - break; - } - - break; - case CON_STATE_READ_QUERY_RESULT: - /* read all packets of the resultset - * - * depending on the backend we may forward the data to the client right away + /* if the packet comes from the server, has a 0x01, is SPNEGO and has 'accept-completed' set, + * the next packet comes from the server too */ - do { - network_socket *recv_sock; - - recv_sock = con->server; - - g_assert(events == 0 || event_fd == recv_sock->fd); - - switch (network_mysqld_read(srv, recv_sock)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; + if (0 != network_mysqld_proto_skip(&packet, 1)) { + /* hmm ... what to do now ? */ + err = 1; + } else if (FALSE == network_asn1_is_valid(&packet, &gerr)) { + g_debug("%s: ASN1 packet is invalid: %s", G_STRLOC, gerr->message); + g_clear_error(&gerr); + err = 1; + } else { + network_spnego_response_token *token; - WAIT_FOR_EVENT(con->server, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_query_result"); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s.%d: network_mysqld_read(CON_STATE_READ_QUERY_RESULT) returned an error", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; - } - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + token = network_spnego_response_token_new(); - switch (plugin_call(srv, con, con->state)) { - case NETWORK_SOCKET_SUCCESS: - /* if we don't need the resultset, forward it to the client */ - if (!con->resultset_is_finished && !con->resultset_is_needed) { - /* check how much data we have in the queue waiting, no need to try to send 5 bytes */ - if (con->client->send_queue->len > 64 * 1024) { - con->state = CON_STATE_SEND_QUERY_RESULT; - } + if (TRUE == network_spnego_proto_get_response_token(&packet, token, &gerr)) { + if (token->negState != SPNEGO_RESPONSE_STATE_ACCEPT_INCOMPLETE) { + con->auth_next_packet_is_from_server = TRUE; } - break; - case NETWORK_SOCKET_ERROR: - /* something nasty happend, let's close the connection */ - con->state = CON_STATE_ERROR; - break; - default: - g_critical("%s.%d: ...", __FILE__, __LINE__); - con->state = CON_STATE_ERROR; - break; + } else { + g_debug("%s: parsing spnego failed: %s", G_STRLOC, gerr->message); + /* do we care why it failed ? */ + g_clear_error(&gerr); } + network_spnego_response_token_free(token); + } + } + } + return err ? -1 : 0; +} - } while (con->state == CON_STATE_READ_QUERY_RESULT); - - break; - case CON_STATE_SEND_QUERY_RESULT: - /** - * send the query result-set to the client */ - switch (network_mysqld_write(srv, con->client)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; +/** + * handle the different states of the MySQL protocol + * + * @param event_fd fd on which the event was fired + * @param events the event that was fired + * @param user_data the connection handle + */ +void network_mysqld_con_handle(int event_fd, short events, void *user_data) { + network_mysqld_con_state_t ostate; + network_mysqld_con *con = user_data; + chassis *srv = con->srv; + int retval; + network_socket_retval_t call_ret; - WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_query_result"); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - /** - * client is gone away - * - * close the connection and clean up - */ - con->state = CON_STATE_ERROR; - break; - } + g_assert(srv); + g_assert(con); - /* if the write failed, don't call the plugin handlers */ - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + if (events == EV_READ) { - /* in case we havn't read the full resultset from the server yet, go back and read more - */ - if (!con->resultset_is_finished && con->server) { - con->state = CON_STATE_READ_QUERY_RESULT; - break; - } + network_mysqld_con_handle_evread(event_fd, con); - switch (plugin_call(srv, con, con->state)) { + } else if (events == EV_TIMEOUT) { + /* if we got a timeout on CON_STATE_CONNECT_SERVER we should pick another backend */ + switch ((retval = plugin_call_timeout(srv, con))) { case NETWORK_SOCKET_SUCCESS: + /* the plugin did set a reasonable next state */ break; default: con->state = CON_STATE_ERROR; break; - } - - /* special treatment for the LOAD DATA LOCAL INFILE command */ - if (con->state != CON_STATE_ERROR && - con->parse.command == COM_QUERY && - 1 == network_mysqld_com_query_result_is_local_infile(con->parse.data)) { - con->state = CON_STATE_READ_LOCAL_INFILE_DATA; - } - - break; - case CON_STATE_READ_LOCAL_INFILE_DATA: { - /** - * read the file content from the client - */ - network_socket *recv_sock; - - recv_sock = con->client; - - /** - * LDLI is usually a whole set of packets - */ - do { - switch (network_mysqld_read(srv, recv_sock)) { - case NETWORK_SOCKET_SUCCESS: - break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; - /* call us again when you have a event */ - WAIT_FOR_EVENT(recv_sock, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_load_infile_data"); - - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s: network_mysqld_read(%s) returned an error", - G_STRLOC, - network_mysqld_con_state_get_name(ostate)); - con->state = CON_STATE_ERROR; - break; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ + } + } - /** - * do the plugin call to decode the result-set to track if we are finished already - * or we need to keep reading the data - */ - switch ((call_ret = plugin_call(srv, con, con->state))) { - case NETWORK_SOCKET_SUCCESS: - /** - * if we still haven't read all data from LDLI, lets forward immediatly - * the data to the backends so that we can read the next packets - */ - if (!con->local_file_data_is_finished && con->server) { - con->state = CON_STATE_SEND_LOCAL_INFILE_DATA; - } + /** + * loop on the same connection as long as we don't end up in a stable state + */ - break; - default: - g_critical("%s: plugin_call(%s) unexpected return value: %d", - G_STRLOC, - network_mysqld_con_state_get_name(ostate), - call_ret); + if (event_fd != -1) { + NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::done"); + } else { + NETWORK_MYSQLD_CON_TRACK_TIME(con, "con_handle_start"); + } - con->state = CON_STATE_ERROR; - break; - } - /* read packets from the network until the plugin decodes to go to the next state */ - } while (con->state == CON_STATE_READ_LOCAL_INFILE_DATA); - - break; } - case CON_STATE_SEND_LOCAL_INFILE_DATA: - /* send the hand-shake to the client and wait for a response */ + do { + struct timeval timeout; - switch (network_mysqld_write(srv, con->server)) { - case NETWORK_SOCKET_SUCCESS: - /* if we still haven't read all data from LDLI so we need to go back and read more - */ - if (!con->local_file_data_is_finished && con->server) { - con->state = CON_STATE_READ_LOCAL_INFILE_DATA; - } - /* we have read all data from LDLI so we need to read the LDLI result from the server - */ - else { - con->state = CON_STATE_READ_LOCAL_INFILE_RESULT; - } + ostate = con->state; +#ifdef NETWORK_DEBUG_TRACE_STATE_CHANGES + /* if you need the state-change information without dtrace, enable this */ + g_debug("%s: [%d] %s", + G_STRLOC, + getpid(), + network_mysqld_con_state_get_name(con->state)); +#endif + MYSQLPROXY_STATE_CHANGE(event_fd, events, con->state); + switch (con->state) { + case CON_STATE_ERROR: + network_mysqld_con_state_error(event_fd, con, events); + con = NULL; + return; break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; - - WAIT_FOR_EVENT(con->server, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_load_infile_data"); - + case CON_STATE_CLOSE_CLIENT: + case CON_STATE_CLOSE_SERVER: + network_mysqld_con_state_close(event_fd, con); + con = NULL; return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - /** - * writing failed, closing connection - */ - con->state = CON_STATE_ERROR; break; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ - - switch ((call_ret = plugin_call(srv, con, con->state))) { - case NETWORK_SOCKET_SUCCESS: + case CON_STATE_INIT: + network_mysqld_con_state_init(event_fd, con); break; - default: - g_critical("%s: plugin_call(%s) unexpected return value: %d", - G_STRLOC, - network_mysqld_con_state_get_name(ostate), - call_ret); - - con->state = CON_STATE_ERROR; + case CON_STATE_CONNECT_SERVER: + if (network_mysqld_con_state_connect_server(event_fd, con, &ostate)) + return; break; - } - - break; - case CON_STATE_READ_LOCAL_INFILE_RESULT: { - /** - * read auth data from the remote mysql-server - */ - network_socket *recv_sock; - recv_sock = con->server; - g_assert(events == 0 || event_fd == recv_sock->fd); - - switch (network_mysqld_read(srv, recv_sock)) { - case NETWORK_SOCKET_SUCCESS: + case CON_STATE_READ_HANDSHAKE: + g_assert(events == 0 || event_fd == con->server->fd); + if (network_mysqld_con_state_read_handshake(event_fd, con, ostate)) + return; break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->read_timeout; - - /* call us again when you have a event */ - WAIT_FOR_EVENT(recv_sock, EV_READ, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::read_load_infile_result"); - - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s: network_mysqld_read(%s) returned an error", - G_STRLOC, - network_mysqld_con_state_get_name(ostate)); - - con->state = CON_STATE_ERROR; + case CON_STATE_SEND_HANDSHAKE: + /* send the hand-shake to the client and wait for a response */ + if (network_mysqld_con_state_send_handshake(event_fd, con, ostate)) + return; break; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ - - switch ((call_ret = plugin_call(srv, con, con->state))) { - case NETWORK_SOCKET_SUCCESS: + case CON_STATE_READ_AUTH: + /* read auth from client */ + g_assert(events == 0 || event_fd == con->client->fd); + if (network_mysqld_con_state_read_auth(event_fd, con, ostate)) + return; + break; + case CON_STATE_SEND_AUTH: + /* send the auth-response to the server */ + if (network_mysqld_con_state_send_auth(event_fd, con, ostate)) + return; break; - default: - g_critical("%s: plugin_call(%s) unexpected return value: %d", - G_STRLOC, - network_mysqld_con_state_get_name(ostate), - call_ret); - - con->state = CON_STATE_ERROR; + case CON_STATE_READ_AUTH_RESULT: + /* read the auth result from the server */ + g_assert(events == 0 || event_fd == con->server->fd); + if (network_mysqld_con_state_read_auth_result(event_fd, con, ostate)) + return; break; - } - - break; } - case CON_STATE_SEND_LOCAL_INFILE_RESULT: - /* send the hand-shake to the client and wait for a response */ - - switch (network_mysqld_write(srv, con->client)) { - case NETWORK_SOCKET_SUCCESS: + case CON_STATE_SEND_AUTH_RESULT: + /* send the hand-shake to the client and wait for a response */ + if (network_mysqld_con_state_send_auth_result(event_fd, con, ostate)) + return; break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; - - WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_load_infile_result"); - - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: + case CON_STATE_READ_AUTH_OLD_PASSWORD: + /* read auth from client */ + if (network_mysqld_con_state_read_auth_old_password(event_fd, con, ostate)) + return; + break; + case CON_STATE_SEND_AUTH_OLD_PASSWORD: + /* send the auth-response to the server */ + if (network_mysqld_con_state_send_auth_old_password(event_fd, con, ostate)) + return; + break; + case CON_STATE_READ_QUERY: + g_assert(events == 0 || event_fd == con->client->fd); + if (network_mysqld_con_state_read_query(event_fd, con, ostate)) + return; + break; + case CON_STATE_SEND_QUERY: + /* send the query to the server + * + * this state will loop until all the packets from the send-queue are flushed + */ + if (network_mysqld_con_state_send_query(event_fd, con, ostate)) + return; + break; + case CON_STATE_READ_QUERY_RESULT: + /* read all packets of the resultset + * + * depending on the backend we may forward the data to the client right away + */ + g_assert(events == 0 || event_fd == con->server->fd); + if (network_mysqld_con_state_read_query_result(event_fd, con, ostate)) + return; + break; + case CON_STATE_SEND_QUERY_RESULT: + /** + * send the query result-set to the client */ + if (network_mysqld_con_state_send_query_result(event_fd, con, ostate)) + return; + break; + case CON_STATE_READ_LOCAL_INFILE_DATA: /** - * writing failed, closing connection + * read the file content from the client */ - con->state = CON_STATE_ERROR; + if (network_mysqld_con_state_read_local_infile_data(event_fd, con, ostate)) + return; break; - } - - if (con->state != ostate) break; /* the state has changed (e.g. CON_STATE_ERROR) */ - - switch ((call_ret = plugin_call(srv, con, con->state))) { - case NETWORK_SOCKET_SUCCESS: + case CON_STATE_SEND_LOCAL_INFILE_DATA: + /* send the hand-shake to the client and wait for a response */ + if (network_mysqld_con_state_send_local_infile_data(event_fd, con, ostate)) + return; break; - default: - g_critical("%s: plugin_call(%s) unexpected return value: %d", - G_STRLOC, - network_mysqld_con_state_get_name(ostate), - call_ret); - - con->state = CON_STATE_ERROR; + case CON_STATE_READ_LOCAL_INFILE_RESULT: + /** + * read auth data from the remote mysql-server + */ + g_assert(events == 0 || event_fd == con->server->fd); + if (network_mysqld_con_state_read_local_infile_result(event_fd, con, ostate)) + return; break; - } - - break; - case CON_STATE_SEND_ERROR: - /** - * send error to the client - * and close the connections afterwards - * */ - switch (network_mysqld_write(srv, con->client)) { - case NETWORK_SOCKET_SUCCESS: + case CON_STATE_SEND_LOCAL_INFILE_RESULT: + /* send the hand-shake to the client and wait for a response */ + if (network_mysqld_con_state_send_local_infile_result(event_fd, con, ostate)) + return; break; - case NETWORK_SOCKET_WAIT_FOR_EVENT: - timeout = con->write_timeout; - - WAIT_FOR_EVENT(con->client, EV_WRITE, &timeout); - NETWORK_MYSQLD_CON_TRACK_TIME(con, "wait_for_event::send_error"); - return; - case NETWORK_SOCKET_ERROR_RETRY: - case NETWORK_SOCKET_ERROR: - g_critical("%s.%d: network_mysqld_write(CON_STATE_SEND_ERROR) returned an error", __FILE__, __LINE__); - - con->state = CON_STATE_ERROR; + case CON_STATE_SEND_ERROR: + /** + * send error to the client + * and close the connections afterwards + * */ + network_mysqld_con_state_send_error(event_fd, con); break; - } - - con->state = CON_STATE_CLOSE_CLIENT; - - break; } event_fd = -1; events = 0; + } while (ostate != con->state); NETWORK_MYSQLD_CON_TRACK_TIME(con, "con_handle_end"); @@ -2038,11 +2498,11 @@ * * in other words: this code below should never be triggered. */ - g_critical("%s: left the MySQL protocol's state-machine at state '%s'. You may see the connection hang now.", + g_critical("%s: left the MySQL protocol's state-machine at state '%s'. " + "You may see the connection hang now.", G_STRLOC, network_mysqld_con_state_get_name(con->state)); - return; } diff -rua mysql-proxy-0.8.3.orig/src//network-mysqld.h mysql-proxy-0.8.3/src//network-mysqld.h --- mysql-proxy-0.8.3.orig/src//network-mysqld.h 2012-08-06 13:42:12.000000000 +0200 +++ mysql-proxy-0.8.3/src//network-mysqld.h 2012-10-02 12:19:49.000000000 +0200 @@ -418,4 +418,5 @@ NETWORK_API int network_mysqld_queue_reset(network_socket *sock); NETWORK_API int network_mysqld_queue_sync(network_socket *dst, network_socket *src); + #endif diff -rua mysql-proxy-0.8.3.orig/src//network-mysqld-lua.c mysql-proxy-0.8.3/src//network-mysqld-lua.c --- mysql-proxy-0.8.3.orig/src//network-mysqld-lua.c 2012-08-06 13:42:12.000000000 +0200 +++ mysql-proxy-0.8.3/src//network-mysqld-lua.c 2012-10-10 13:37:39.000000000 +0200 @@ -876,3 +876,24 @@ } +const char * +network_mysqld_lua_stmt_ret_get_name (network_mysqld_lua_stmt_ret r) { + + const char *ans = NULL; + + if (r == PROXY_NO_DECISION) + ans = "PROXY_NO_DECISION"; + else if (r == PROXY_SEND_QUERY) + ans = "PROXY_SEND_QUERY"; + else if (r == PROXY_SEND_RESULT) + ans = "PROXY_SEND_RESULT"; + else if (r == PROXY_SEND_INJECTION) + ans = "PROXY_SEND_INJECTION"; + else if (r == PROXY_IGNORE_RESULT) + ans = "PROXY_IGNORE_RESULT"; + else + ans = "unknown"; + + return ans; +} + diff -rua mysql-proxy-0.8.3.orig/src//network-mysqld-lua.h mysql-proxy-0.8.3/src//network-mysqld-lua.h --- mysql-proxy-0.8.3.orig/src//network-mysqld-lua.h 2012-08-06 13:42:12.000000000 +0200 +++ mysql-proxy-0.8.3/src//network-mysqld-lua.h 2012-10-02 09:44:19.000000000 +0200 @@ -82,4 +82,6 @@ NETWORK_API network_mysqld_register_callback_ret network_mysqld_con_lua_register_callback(network_mysqld_con *con, const char *lua_script); NETWORK_API int network_mysqld_con_lua_handle_proxy_response(network_mysqld_con *con, const char *lua_script); +NETWORK_API const char *network_mysqld_lua_stmt_ret_get_name(network_mysqld_lua_stmt_ret); + #endif diff -rua mysql-proxy-0.8.3.orig/src//network-mysqld-packet.c mysql-proxy-0.8.3/src//network-mysqld-packet.c --- mysql-proxy-0.8.3.orig/src//network-mysqld-packet.c 2012-08-06 13:42:12.000000000 +0200 +++ mysql-proxy-0.8.3/src//network-mysqld-packet.c 2012-10-04 11:47:23.000000000 +0200 @@ -314,7 +314,7 @@ * check if the we are in the LOCAL INFILE 'send data from client' state */ gboolean network_mysqld_com_query_result_is_local_infile(network_mysqld_com_query_result_t *udata) { - return (udata->state == PARSE_COM_QUERY_LOCAL_INFILE_DATA) ? TRUE : FALSE; + return (udata && udata->state == PARSE_COM_QUERY_LOCAL_INFILE_DATA) ? TRUE : FALSE; } network_mysqld_com_stmt_prepare_result_t *network_mysqld_com_stmt_prepare_result_new() { diff -rua mysql-proxy-0.8.3.orig/src//network-mysqld-proto.c mysql-proxy-0.8.3/src//network-mysqld-proto.c --- mysql-proxy-0.8.3.orig/src//network-mysqld-proto.c 2012-08-06 13:42:12.000000000 +0200 +++ mysql-proxy-0.8.3/src//network-mysqld-proto.c 2012-10-10 13:36:38.000000000 +0200 @@ -1116,4 +1116,49 @@ return network_mysqld_proto_skip(packet, NET_HEADER_SIZE); } + +const char *network_mysqld_proto_get_cmd_name(enum enum_server_command cmd) { + + const char *ans; + + switch (cmd) { + + case COM_SLEEP: ans = "COM_SLEEP"; break; + case COM_QUIT: ans = "COM_QUIT"; break; + case COM_INIT_DB: ans = "COM_INIT_DB"; break; + case COM_QUERY: ans = "COM_QUERY"; break; + case COM_FIELD_LIST: ans = "COM_FIELD_LIST"; break; + case COM_CREATE_DB: ans = "COM_CREATE_DB"; break; + case COM_DROP_DB: ans = "COM_DROP_DB"; break; + case COM_REFRESH: ans = "COM_REFRESH"; break; + case COM_SHUTDOWN: ans = "COM_SHUTDOWN"; break; + case COM_STATISTICS: ans = "COM_STATISTICS"; break; + case COM_PROCESS_INFO: ans = "COM_PROCESS_INFO"; break; + case COM_CONNECT: ans = "COM_CONNECT"; break; + case COM_PROCESS_KILL: ans = "COM_PROCESS_KILL"; break; + case COM_DEBUG: ans = "COM_DEBUG"; break; + case COM_PING: ans = "COM_PING"; break; + case COM_TIME: ans = "COM_TIME"; break; + case COM_DELAYED_INSERT: ans = "COM_DELAYED_INSERT"; break; + case COM_CHANGE_USER: ans = "COM_CHANGE_USER"; break; + case COM_BINLOG_DUMP: ans = "COM_BINLOG_DUMP"; break; + case COM_TABLE_DUMP: ans = "COM_TABLE_DUMP"; break; + case COM_CONNECT_OUT: ans = "COM_CONNECT_OUT"; break; + case COM_REGISTER_SLAVE: ans = "COM_REGISTER_SLAVE"; break; + case COM_STMT_PREPARE: ans = "COM_STMT_PREPARE"; break; + case COM_STMT_EXECUTE: ans = "COM_STMT_EXECUTE"; break; + case COM_STMT_SEND_LONG_DATA: ans = "COM_STMT_SEND_LONG_DATA"; break; + case COM_STMT_CLOSE: ans = "COM_STMT_CLOSE"; break; + case COM_STMT_RESET: ans = "COM_STMT_RESET"; break; + case COM_SET_OPTION: ans = "COM_SET_OPTION"; break; + case COM_STMT_FETCH: ans = "COM_STMT_FETCH"; break; + case COM_DAEMON: ans = "COM_DAEMON"; break; + case COM_END: ans = "COM_END"; break; + default: ans = "unknown"; break; + } + + return ans; +} + + /*@}*/ diff -rua mysql-proxy-0.8.3.orig/src//network-mysqld-proto.h mysql-proxy-0.8.3/src//network-mysqld-proto.h --- mysql-proxy-0.8.3.orig/src//network-mysqld-proto.h 2012-08-06 13:42:12.000000000 +0200 +++ mysql-proxy-0.8.3/src//network-mysqld-proto.h 2012-10-10 13:35:52.000000000 +0200 @@ -140,4 +140,6 @@ const char *response, gsize response_len, const char *double_hashed, gsize double_hashed_len); +NETWORK_API const char *network_mysqld_proto_get_cmd_name(enum enum_server_command); + #endif