X Tutup
/* $%BEGINLICENSE%$ Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA $%ENDLICENSE%$ */ #include #include #include #include #include "network-mysqld.h" #include "network-mysqld-proto.h" #include "network-mysqld-packet.h" #include #include #include #include "sys-pedantic.h" #include "string-len.h" #include /** * debug plugin * * gives access to anything in the proxy that is exported into lua-land * * all commands will be executed by lua_pcall() and a result-set or error-packet * will be returned */ struct chassis_plugin_config { gchar *address; /**< listening address of the debug interface */ network_mysqld_con *listen_con; }; static int lua_table_key_to_mysql_field(lua_State *L, GPtrArray *fields) { MYSQL_FIELD *field = NULL; field = network_mysqld_proto_fielddef_new(); if (lua_isstring(L, -2) && !lua_isnumber(L, -2)) { /* is-string is true for strings AND numbers * but a tostring() is changing a number into a * string and that trashes the lua_next() call */ field->name = g_strdup(lua_tostring(L, -2)); } else if (lua_isnumber(L, -2)) { field->name = g_strdup_printf("%ld", lua_tointeger(L, -2)); } else { /* we don't know how to convert the key */ field->name = g_strdup("(hmm)"); } field->type = FIELD_TYPE_VAR_STRING; /* STRING matches all values */ g_ptr_array_add(fields, field); return 0; } int plugin_debug_con_handle_stmt(chassis *chas, network_mysqld_con *con, GString *s) { gsize i, j; GPtrArray *fields; GPtrArray *rows; GPtrArray *row; switch(s->str[NET_HEADER_SIZE]) { case COM_QUERY: fields = NULL; rows = NULL; row = NULL; /* support the basic commands sent by the mysql shell */ if (0 == g_ascii_strncasecmp(s->str + NET_HEADER_SIZE + 1, C("select @@version_comment limit 1"))) { MYSQL_FIELD *field; fields = network_mysqld_proto_fielddefs_new(); field = network_mysqld_proto_fielddef_new(); field->name = g_strdup("@@version_comment"); field->type = FIELD_TYPE_VAR_STRING; g_ptr_array_add(fields, field); rows = g_ptr_array_new(); row = g_ptr_array_new(); g_ptr_array_add(row, g_strdup("MySQL Enterprise Agent")); g_ptr_array_add(rows, row); network_mysqld_con_send_resultset(con->client, fields, rows); } else if (0 == g_ascii_strncasecmp(s->str + NET_HEADER_SIZE + 1, C("select USER()"))) { MYSQL_FIELD *field; fields = network_mysqld_proto_fielddefs_new(); field = network_mysqld_proto_fielddef_new(); field->name = g_strdup("USER()"); field->type = FIELD_TYPE_VAR_STRING; g_ptr_array_add(fields, field); rows = g_ptr_array_new(); row = g_ptr_array_new(); g_ptr_array_add(row, g_strdup("root")); g_ptr_array_add(rows, row); network_mysqld_con_send_resultset(con->client, fields, rows); } else { MYSQL_FIELD *field = NULL; lua_State *L = chas->sc->L; if (0 == luaL_loadstring(L, s->str + NET_HEADER_SIZE + 1) && 0 == lua_pcall(L, 0, 1, 0)) { /* let's see what is on the stack * - scalars are turned into strings * return "foo" * - 1-dim tables are turned into a single-row result-set * return { foo = "bar", baz = "foz" } * - 2-dim tables are turned into a multi-row result-set * return { { foo = "bar" }, { "foz" } } */ switch (lua_type(L, -1)) { case LUA_TTABLE: /* take the names from the fields */ fields = network_mysqld_proto_fielddefs_new(); lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_istable(L, -1)) { /* 2-dim table * * we only only take the keys from the first row * afterwards we ignore them */ lua_pushnil(L); while (lua_next(L, -2) != 0) { if (!rows) { /* this is the 1st round, add the keys */ lua_table_key_to_mysql_field(L, fields); } if (!row) row = g_ptr_array_new(); if (lua_isboolean(L, -1)) { g_ptr_array_add(row, g_strdup(lua_toboolean(L, -1) ? "TRUE" : "FALSE")); } else if (lua_isnumber(L, -1)) { g_ptr_array_add(row, g_strdup_printf("%.0f", lua_tonumber(L, -1))); } else { g_ptr_array_add(row, g_strdup(lua_tostring(L, -1))); } lua_pop(L, 1); /* pop the value, but keep the key on the stack */ } if (!rows) rows = g_ptr_array_new(); g_ptr_array_add(rows, row); row = NULL; } else { /* 1-dim table */ lua_table_key_to_mysql_field(L, fields); if (!row) row = g_ptr_array_new(); if (lua_isboolean(L, -1)) { g_ptr_array_add(row, g_strdup(lua_toboolean(L, -1) ? "TRUE" : "FALSE")); } else if (lua_isnumber(L, -1)) { g_ptr_array_add(row, g_strdup_printf("%.0f", lua_tonumber(L, -1))); } else { g_ptr_array_add(row, g_strdup(lua_tostring(L, -1))); } } lua_pop(L, 1); /* pop the value, but keep the key on the stack */ } if (row) { /* in 1-dim we have to append the row to the result-set, * in 2-dim this is already done and row is NULL */ if (!rows) rows = g_ptr_array_new(); g_ptr_array_add(rows, row); } break; default: /* a scalar value */ fields = network_mysqld_proto_fielddefs_new(); field = network_mysqld_proto_fielddef_new(); field->name = g_strdup("lua"); field->type = FIELD_TYPE_VAR_STRING; g_ptr_array_add(fields, field); rows = g_ptr_array_new(); row = g_ptr_array_new(); g_ptr_array_add(row, g_strdup(lua_tostring(L, -1))); g_ptr_array_add(rows, row); break; } lua_pop(L, 1); /* get rid of the result */ network_mysqld_con_send_resultset(con->client, fields, rows); } /* if we don't have fields for the resultset, we should have a * error-msg on the stack */ if (!fields) { size_t err_len = 0; const char *err; err = lua_tolstring(L, -1, &err_len); network_mysqld_con_send_error(con->client, err, err_len); lua_pop(L, 1); } } /* clean up */ if (fields) { network_mysqld_proto_fielddefs_free(fields); fields = NULL; } if (rows) { for (i = 0; i < rows->len; i++) { row = rows->pdata[i]; for (j = 0; j < row->len; j++) { g_free(row->pdata[j]); } g_ptr_array_free(row, TRUE); } g_ptr_array_free(rows, TRUE); rows = NULL; } break; case COM_QUIT: break; case COM_INIT_DB: network_mysqld_con_send_ok(con->client); break; default: network_mysqld_con_send_error(con->client, C("unknown COM_*")); break; } return 0; } NETWORK_MYSQLD_PLUGIN_PROTO(server_con_init) { network_mysqld_auth_challenge *auth; GString *packet; auth = network_mysqld_auth_challenge_new(); auth->server_version_str = g_strdup("5.1.99-proxy-debug"); auth->thread_id = 1; auth->charset = 0x08; auth->server_status = SERVER_STATUS_AUTOCOMMIT | 0; network_mysqld_auth_challenge_set_challenge(auth); packet = g_string_new(NULL); network_mysqld_proto_append_auth_challenge(packet, auth); network_mysqld_queue_append(con->client, con->client->send_queue, S(packet)); g_string_free(packet, TRUE); network_mysqld_auth_challenge_free(auth); con->state = CON_STATE_SEND_HANDSHAKE; return NETWORK_SOCKET_SUCCESS; } NETWORK_MYSQLD_PLUGIN_PROTO(server_read_auth) { GString *s; GList *chunk; network_socket *recv_sock, *send_sock; recv_sock = con->client; chunk = recv_sock->recv_queue->chunks->tail; s = chunk->data; /* the password is fine */ send_sock = con->client; network_mysqld_con_send_ok(send_sock); g_string_free(chunk->data, TRUE); g_queue_delete_link(recv_sock->recv_queue->chunks, chunk); con->state = CON_STATE_SEND_AUTH_RESULT; return NETWORK_SOCKET_SUCCESS; } NETWORK_MYSQLD_PLUGIN_PROTO(server_read_query) { GString *s; GList *chunk; network_socket *recv_sock; recv_sock = con->client; chunk = recv_sock->recv_queue->chunks->tail; s = chunk->data; plugin_debug_con_handle_stmt(chas, con, s); g_string_free(chunk->data, TRUE); g_queue_delete_link(recv_sock->recv_queue->chunks, chunk); con->state = CON_STATE_SEND_QUERY_RESULT; return NETWORK_SOCKET_SUCCESS; } static int network_mysqld_server_connection_init(network_mysqld_con *con) { con->plugins.con_init = server_con_init; con->plugins.con_read_auth = server_read_auth; con->plugins.con_read_query = server_read_query; return 0; } static chassis_plugin_config *network_mysqld_debug_plugin_new(void) { chassis_plugin_config *config; config = g_new0(chassis_plugin_config, 1); return config; } static void network_mysqld_debug_plugin_free(chassis_plugin_config *config) { if (config->listen_con) { /* the socket will be freed by network_mysqld_free() */ } if (config->address) { g_free(config->address); } g_free(config); } /** * add the proxy specific options to the cmdline interface */ static GOptionEntry * network_mysqld_debug_plugin_get_options(chassis_plugin_config *config) { guint i; static GOptionEntry config_entries[] = { { "debug-address", 0, 0, G_OPTION_ARG_STRING, NULL, "listening address:port of the debug-server (default: :4043)", "" }, { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } }; i = 0; config_entries[i++].arg_data = &(config->address); return config_entries; } /** * init the plugin with the parsed config */ static int network_mysqld_debug_plugin_apply_config(chassis *chas, chassis_plugin_config *config) { network_mysqld_con *con; network_socket *listen_sock; if (!config->address) config->address = g_strdup(":4043"); /** * create a connection handle for the listen socket */ con = network_mysqld_con_new(); network_mysqld_add_connection(chas, con); con->config = config; config->listen_con = con; listen_sock = network_socket_new(); con->server = listen_sock; /* set the plugin hooks as we want to apply them to the new connections too later */ network_mysqld_server_connection_init(con); /* FIXME: network_socket_set_address() */ if (0 != network_address_set_address(listen_sock->dst, config->address)) { return -1; } /* FIXME: network_socket_bind() */ if (0 != network_socket_bind(listen_sock)) { return -1; } /** * call network_mysqld_con_accept() with this connection when we are done */ event_set(&(listen_sock->event), listen_sock->fd, EV_READ|EV_PERSIST, network_mysqld_con_accept, con); event_base_set(chas->event_base, &(listen_sock->event)); event_add(&(listen_sock->event), NULL); return 0; } G_MODULE_EXPORT int plugin_init(chassis_plugin *p) { p->magic = CHASSIS_PLUGIN_MAGIC; p->name = g_strdup("debug"); p->version = g_strdup(PACKAGE_VERSION); p->init = network_mysqld_debug_plugin_new; p->get_options = network_mysqld_debug_plugin_get_options; p->apply_config = network_mysqld_debug_plugin_apply_config; p->destroy = network_mysqld_debug_plugin_free; return 0; }
X Tutup