/* Copyright (c) 2022, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. 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, version 2.0, 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 */ #include "mysql_server_event_tracking_bridge_imp.h" #include "sql/command_mapping.h" #include "sql/current_thd.h" #include "sql/mysqld.h" /* sql_statement_names */ #include "sql/sql_audit.h" #include "sql/sql_class.h" #include "sql/sql_lex.h" #include "sql/sql_plugin.h" #include "sql/sql_rewrite.h" #include "my_sys.h" #include "mysql/components/services/log_builtins.h" /* LogErr */ #include "mysql/plugin_audit.h" #include /** @file sql/server_component/mysql_server_event_tracking_bridge_imp.cc A bridge implementation that translates event tracking APIs to audit plugin APIs. */ // clang-format off /** @page page_event_tracking_services Event Tracking Services @section section_introduction Introduction Event tracking services are a group of services that allow implementor to monitor various database activities. Two main actors involved are: - Producers: Those who emit one or more events - Consumers: Those who are interesting in processing events generated by consumers Following services are part of this groups of services. - Events related to user authentication (@ref s_mysql_event_tracking_authentication) - Events related to COM command execution (@ref s_mysql_event_tracking_command) - Events related to a session connection (@ref s_mysql_event_tracking_connection) - Events related to execution status (@ref s_mysql_event_tracking_general) - Events related to global variable access (@ref s_mysql_event_tracking_global_variable) - Events related to program lifecycle (@ref s_mysql_event_tracking_lifecycle) - Events to emit special message to log (@ref s_mysql_event_tracking_message) - Events to perform query rewrite (@ref s_mysql_event_tracking_parse) - Events to track query execution (@ref s_mysql_event_tracking_query) - Events to track stored program execution (@ref s_mysql_event_tracking_stored_program) - Events to track table access (@ref s_mysql_event_tracking_table_access) When a consumer component is installed, it registeres its interest in receiving notification for any subset of events by implementing corresponding services. A producer, as a part of generating an event will inform all consumers who have registered interest of a given event. Through some of the above mentioned events, a consumer can indicate producer to either generate an error/warning or stop the execution or both. server component, which is part of MySQL server is one example of producer component. At different point of execution, it emits events of all classes of events mentioned above. Please take a look at following pages for more details: - @subpage page_event_tracking_event_details - @subpage page_event_tracking_new_consumer - @subpage page_event_tracking_new_producer */ /** @page page_event_tracking_event_details Details of existing event tracking services @section section_event_tracking_user_management Events related to user management User management events are produced by server component as part of various user management DDL. @sa @ref s_mysql_event_tracking_authentication - EVENT_TRACKING_AUTHENTICATION_FLUSH: Emitted as a part of FLUSH PRIVILEGES - EVENT_TRACKING_AUTHENTICATION_AUTHID_CREATE: Emitted as a part of user creation through CREATE USER - EVENT_TRACKING_AUTHENTICATION_CREDENTIAL_CHANGE: Emitted as a part of credential change through ALTER USER or SET PASSWORD - EVENT_TRACKING_AUTHENTICATION_AUTHID_RENAME: Emitted as a part of RENAME USER - EVENT_TRACKING_AUTHENTICATION_AUTHID_DROP: Emitted as a part of DROP USER Refer to @ref mysql_event_tracking_authentication_data to know more about data passed to consumer. In order to provide additional information for above events, server component implements event_tracking_authentication_information (@ref s_mysql_event_tracking_authentication_information) service that a consumer component can use. @section section_event_tracking_command Events related to COM command execution Commands events are executed whenever COM_* commands are executed. @sa @ref s_mysql_event_tracking_command - EVENT_TRACKING_COMMAND_START: Emitted whenever server component starts processing of COM_* command - EVENT_TRACKING_COMMAND_END: Emitted whenever server component is done processing a given COM_* command Refer to @ref mysql_event_tracking_command_data to know more about data passed to consumer. @section section_event_tracking_connection Events related to a session connection Connection events are generated at various stages of connection lifecycle. @sa @ref s_mysql_event_tracking_connection - EVENT_TRACKING_CONNECTION_PRE_AUTHENTICATE: Emitted whenever a new connection request from a client is received. - EVENT_TRACKING_CONNECTION_CONNECT: Emitted once authentication is completed for a new incoming connection - EVENT_TRACKING_CONNECTION_CHANGE_USER: Emitted when server receives CHANGE USER request on an existing connection - EVENT_TRACKING_CONNECTION_DISCONNECT: Emitted when an establish session is diconnecting. Refer to @ref mysql_event_tracking_connection_data to know more about data passed to consumer. @section section_event_tracking_general Events related to execution status Execution status events occur at various stages of SQL execution. @sa @ref s_mysql_event_tracking_general - EVENT_TRACKING_GENERAL_LOG: Emitted whenever an SQL statement is logged into server's general log - EVENT_TRACKING_GENERAL_ERROR: Emitted whenever an error is raised - EVENT_TRACKING_GENERAL_RESULT: Emitted once result is transmitted to user - EVENT_TRACKING_GENERAL_STATUS: Emitted once query execution is complete Refer to @ref mysql_event_tracking_general_data to know more about data passed to consumer. In order to provide additional information for above events, server component implements event_tracking_general_information (@ref s_mysql_event_tracking_general_information) service that a consumer component can use. @section section_event_tracking_global_variable Events related to global variable access Global variable events occur whenever a global system variable is queried or is set. @sa @ref s_mysql_event_tracking_global_variable - EVENT_TRACKING_GLOBAL_VARIABLE_GET: Emitted whenever a global system variable value is queried. - EVENT_TRACKING_GLOBAL_VARIABLE_SET: Emitted whenever a global system variable value is set. Refer to @ref mysql_event_tracking_global_variable_data to know more about data passed to consumer. @section section_event_tracking_lifecycle Events related to program lifecycle Lifecycle events occur at program start-up/shutdown. @sa @ref s_mysql_event_tracking_lifecycle - EVENT_TRACKING_STARTUP_STARTUP: Emitted after program startup - EVENT_TRACKING_SHUTDOWN_SHUTDOWN: Emitted before program shutdown Refer to @ref mysql_event_tracking_startup_data and @ref mysql_event_tracking_shutdown_data to know more about data passed to cunsumer. @section section_event_tracking_message Events to emit special message to log Message events are special events to inform a consumer about certain messages that producer want to deliver. A usecase of such a message is to add special markers in e.g. audit log files. @sa @ref s_mysql_event_tracking_message - EVENT_TRACKING_MESSAGE_INTERNAL: Denotes system generated messages - EVENT_TRACKING_MESSAGE_USER: Denotes user generated messages Refer to @ref mysql_event_tracking_message_data to know more about data passed to consumer. @section section_event_tracking_parse Events to perform query rewrite or intercept queries These events allow consumer to either abort an operation or rewrite a query to suite the need. @sa @ref s_mysql_event_tracking_parse - EVENT_TRACKING_PARSE_PREPARSE: Emitted right after query is received but before execution begins - EVENT_TRACKING_PARSE_POSTPARSE: Emitted after parsing phase of the query Refer to @ref mysql_event_tracking_message_data to know more about data passed to consumer. @section section_event_tracking_query Events to track query execution These events occur at the beginning and end of query execution. @sa @ref s_mysql_event_tracking_query - EVENT_TRACKING_QUERY_START: Emitted at the start of top level query execution - EVENT_TRACKING_QUERY_STATUS_END: Emitted at the end of top level query execution - EVENT_TRACKING_QUERY_NESTED_START: Emitted at the start of subquery execution - EVENT_TRACKING_QUERY_NESTED_STATUS_END: Emitted at the end of subquery execution Refer to @ref mysql_event_tracking_query_data to know more about data passed to consumer. @section section_event_tracking_stored_program Events to track stored program execution These events occur whenever a stored program is executed. @sa @ref s_mysql_event_tracking_stored_program - EVENT_TRACKING_STORED_PROGRAM_EXECUTE: Emitted whenever a stored program is executed. Refer to @ref mysql_event_tracking_stored_program_data to know more about data passed to consumer. @section section_event_tracking_table_access Events to track table access These events track different types of table access. @sa @ref s_mysql_event_tracking_table_access - EVENT_TRACKING_TABLE_ACCESS_READ: Emitted whenever a table is read - EVENT_TRACKING_TABLE_ACCESS_INSERT: Emitted whenever data is inserted in a table - EVENT_TRACKING_TABLE_ACCESS_UPDATE: Emitted whenever data is updated in a table - EVENT_TRACKING_TABLE_ACCESS_DELETE: Emitted whenever data is deleted from a table Refer to @ref mysql_event_tracking_table_access_data to know more about data passed to consumer. */ /** @page page_event_tracking_new_consumer How to create a new consumer component A consumer of events essentially need to implement a subset of event tracking services. Once such a consumer is installed, producer will use service APIs to notify it about given set of events that the consumer is interested in. In order to simplify writing the core part of the functionality, we have helper headers with skeleton implementation of each class of events. @sa @ref EVENT_TRACKING_AUTHENTICATION_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_COMMAND_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_CONNECTION_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_GENERAL_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_GLOBAL_VARIABLE_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_LIFECYCLE_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_MESSAGE_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_PARSE_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_QUERY_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_STORED_PROGRAM_CONSUMER_EXAMPLE @sa @ref EVENT_TRACKING_TABLE_ACCESS_CONSUMER_EXAMPLE Following is an example of a consumer that implements various event tracking services, increments simple counters for each event and then displays them through status variables. @code #include "mysql/components/services/component_status_var_service.h" #include "mysql/components/util/event_tracking_authentication_consumer_helper.h" #include "mysql/components/util/event_tracking_command_consumer_helper.h" #include "mysql/components/util/event_tracking_connection_consumer_helper.h" #include "mysql/components/util/event_tracking_general_consumer_helper.h" #include "mysql/components/util/event_tracking_global_variable_consumer_helper.h" #include "mysql/components/util/event_tracking_lifecycle_consumer_helper.h" #include "mysql/components/util/event_tracking_message_consumer_helper.h" #include "mysql/components/util/event_tracking_parse_consumer_helper.h" #include "mysql/components/util/event_tracking_query_consumer_helper.h" #include "mysql/components/util/event_tracking_stored_program_consumer_helper.h" #include "mysql/components/util/event_tracking_table_access_consumer_helper.h" REQUIRES_SERVICE_PLACEHOLDER_AS(status_variable_registration, mysql_status_var_service); namespace Event_tracking_implementation { enum class Event_types { AUTHENTICATION = 0, COMMAND, CONNECTION, GENERAL, GLOBAL_VARIABLE, MESSAGE, PARSE, QUERY, SHUTDOWN, STARTUP, STORED_PROGRAM, TABLE_ACCESS, LAST }; // For the sake of simplicity, locking is ignored // Ideally one should use atomic variables and use // status variable of type SHOW_FUNC to fetch and // display the value unsigned long g_counters[static_cast(Event_types::LAST)] = {0}; static void increment_counter(Event_types event_type) { if (event_type < Event_types::LAST) { ++g_counters[static_cast(event_type)]; } } // Status variables static SHOW_VAR status_vars[] = { {"component_event_tracking_example.counter_event_tracking_authentication", (char *)&g_counters[static_cast(Event_types::AUTHENTICATION)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_command", (char *)&g_counters[static_cast(Event_types::COMMAND)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_connection", (char *)&g_counters[static_cast(Event_types::CONNECTION)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_general", (char *)&g_counters[static_cast(Event_types::GENERAL)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_global_" "variable", (char *)&g_counters[static_cast(Event_types::GLOBAL_VARIABLES)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_message", (char *)&g_counters[static_cast(Event_types::MESSAGE)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_parse", (char *)&g_counters[static_cast(Event_types::PARSE)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_query", (char *)&g_counters[static_cast(Event_types::QUERY)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_" "shutdown", (char *)&g_counters[static_cast(Event_types::SHUTDOWN)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_" "startup", (char *)&g_counters[static_cast(Event_types::STARTUP)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_tracking_stored_" "program", (char *)&g_counters[static_cast(Event_types::STORED_PROGRAM)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, {"component_event_tracking_example.counter_event_track_table_" "access", (char *)&g_counters[static_cast(Event_types::TABLE_ACCESS)], SHOW_LONG, SHOW_SCOPE_GLOBAL}, // null terminator required {nullptr, nullptr, SHOW_UNDEF, SHOW_SCOPE_UNDEF}}; // START: Event tracking implementation mysql_event_tracking_authentication_subclass_t Event_tracking_authentication_implementation::filtered_sub_events = 0; bool Event_tracking_authentication_implementation::callback( const mysql_event_tracking_authentication_data *data [[maybe_unused]]) { increment_counter(Event_types::AUTHENTICATION); return false; } mysql_event_tracking_command_subclass_t Event_tracking_command_implementation::filtered_sub_events = 0; bool Event_tracking_command_implementation::callback( const mysql_event_tracking_command_data *data [[maybe_unused]]) { increment_counter(Event_types::COMMAND); return false; } mysql_event_tracking_connection_subclass_t Event_tracking_connection_implementation::filtered_sub_events = 0; bool Event_tracking_connection_implementation::callback( const mysql_event_tracking_connection_data *data [[maybe_unused]]) { increment_counter(Event_types::CONNECTION); return false; } mysql_event_tracking_general_subclass_t Event_tracking_general_implementation::filtered_sub_events = 0; bool Event_tracking_general_implementation::callback( const mysql_event_tracking_general_data *data [[maybe_unused]]) { increment_counter(Event_types::GENERAL); return false; } mysql_event_tracking_global_variable_subclass_t Event_tracking_global_variable_implementation::filtered_sub_events = 0; bool Event_tracking_global_variable_implementation::callback( const mysql_event_tracking_global_variable_data *data [[maybe_unused]]) { increment_counter(Event_types::GLOBAL_VARIABLE); return false; } mysql_event_tracking_startup_subclass_t Event_tracking_lifecycle_implementation::startup_filtered_sub_events = 0; bool Event_tracking_lifecycle_implementation::callback( const mysql_event_tracking_startup_data *data [[maybe_unused]]) { increment_counter(Event_types::STARTUP); return false; } mysql_event_tracking_shutdown_subclass_t Event_tracking_lifecycle_implementation::shutdown_filtered_sub_events = 0; bool Event_tracking_lifecycle_implementation::callback( const mysql_event_tracking_shutdown_data *data [[maybe_unused]]) { increment_counter(Event_types::SHUTDOWN); return false; } mysql_event_tracking_message_subclass_t Event_tracking_message_implementation::filtered_sub_events = 0; bool Event_tracking_message_implementation::callback( const mysql_event_tracking_message_data *data [[maybe_unused]]) { increment_counter(Event_types::MESSAGE); return false; } mysql_event_tracking_parse_subclass_t Event_tracking_parse_implementation::filtered_sub_events = 0; bool Event_tracking_parse_implementation::callback( mysql_event_tracking_parse_data *data [[maybe_unused]]) { increment_counter(Event_types::PARSE); return false; } mysql_event_tracking_query_subclass_t Event_tracking_query_implementation::filtered_sub_events = 0; bool Event_tracking_query_implementation::callback( const mysql_event_tracking_query_data *data [[maybe_unused]]) { increment_counter(Event_types::QUERY); return false; } mysql_event_tracking_stored_program_subclass_t Event_tracking_stored_program_implementation::filtered_sub_events = 0; bool Event_tracking_stored_program_implementation::callback( const mysql_event_tracking_stored_program_data *data [[maybe_unused]]) { increment_counter(Event_types::STORED_PROGRAM); return false; } mysql_event_tracking_table_access_subclass_t Event_tracking_table_access_implementation::filtered_sub_events = 0; bool Event_tracking_table_access_implementation::callback( const mysql_event_tracking_table_access_data *data [[maybe_unused]]) { increment_counter(Event_types::TABLE_ACCESS); return false; } // STOP: Event tracking implementation static mysql_service_status_t init() { if ((variables_registered = mysql_status_var_service->register_variable(status_vars))) { return 1; } return 0; } static mysql_service_status_t deinit() { if (mysql_status_var_service->unregister_variable(status_vars)) return 1; return 0; } } // namespace Event_tracking_implementation // ======================================================================= // Component declaration related stuff // This component provides implementation of following component services IMPLEMENTS_SERVICE_EVENT_TRACKING_AUTHENTICATION( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_COMMAND( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_CONNECTION( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_GENERAL( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_GLOBAL_VARIABLE( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_LIFECYCLE( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_MESSAGE( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_PARSE( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_QUERY( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_STORED_PROGRAM( component_event_tracking_example); IMPLEMENTS_SERVICE_EVENT_TRACKING_TABLE_ACCESS( component_event_tracking_example); // This component provides following services BEGIN_COMPONENT_PROVIDES(component_event_tracking_example) PROVIDES_SERVICE_EVENT_TRACKING_AUTHENTICATION( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_COMMAND( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_CONNECTION( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_GENERAL( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_GLOBAL_VARIABLE( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_LIFECYCLE( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_MESSAGE( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_PARSE( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_QUERY( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_STORED_PROGRAM( component_event_tracking_example), PROVIDES_SERVICE_EVENT_TRACKING_TABLE_ACCESS( component_event_tracking_example), END_COMPONENT_PROVIDES(); // List of dependencies BEGIN_COMPONENT_REQUIRES(component_event_tracking_example) REQUIRES_SERVICE_AS(status_variable_registration, mysql_status_var_service), END_COMPONENT_REQUIRES(); // Component description BEGIN_COMPONENT_METADATA(component_event_tracking_example) METADATA("mysql.author", "Oracle Corporation"), METADATA("mysql.license", "GPL"), METADATA("component_event_tracking_example", "1"), END_COMPONENT_METADATA(); // Component declaration DECLARE_COMPONENT(component_event_tracking_example, "component_event_tracking_example") Event_tracking_consumer_a::init, Event_tracking_consumer_a::deinit END_DECLARE_COMPONENT(); // Component contained in this library DECLARE_LIBRARY_COMPONENTS &COMPONENT_REF( component_event_tracking_example) END_DECLARE_LIBRARY_COMPONENTS @endcode */ /** @page page_event_tracking_new_producer How to create a new producer component A new producer component would typically mean new set of events being introduced. In such a case, new services should be introduced - just like existing set of event tracking services. E.g. something like following: @code BEGIN_SERVICE_DEFINITION(event_tracking_) DECLARE_BOOL_METHOD(notify, (const mysql__data *data)); END_SERVICE_DEFINITION(event_tracking_) @endcode Once introduced, producer should be able to broadcast the event to all interested consumer. To achieve that, a consumer should do following: - Define a new reference caching change using @ref s_mysql_reference_caching_channel - Create a reference caching cache to obtain references to all interested component - Iterate over the reference cache and notify each consumer component Further, a consumer component may be uninstalled at any time. To facilitate that, the reference cache created by the producer should release corresponding reference. This can be achieved by implementing @ref s_mysql_dynamic_loader_services_unload_notification and refreshing reference caches as a part of it. Reference caching component will take care of informing the producer whenever a component is being uinstalled. In addition, developer may also think about writing wrappers to facilitate development of consumer component. For example see @ref EVENT_TRACKING_GENERAL_CONSUMER_EXAMPLE. */ // clang-format on /** Macro to perform LEX_CSTRING transformation */ #define TO_LEXCSTRING(x) \ { x.str, x.length } using event_tracking_authentication_t = SERVICE_TYPE_NO_CONST(event_tracking_authentication); using event_tracking_command_t = SERVICE_TYPE_NO_CONST(event_tracking_command); using event_tracking_connection_t = SERVICE_TYPE_NO_CONST(event_tracking_connection); using event_tracking_general_t = SERVICE_TYPE_NO_CONST(event_tracking_general); using event_tracking_global_variable_t = SERVICE_TYPE_NO_CONST(event_tracking_global_variable); using event_tracking_lifecycle_t = SERVICE_TYPE_NO_CONST(event_tracking_lifecycle); using event_tracking_message_t = SERVICE_TYPE_NO_CONST(event_tracking_message); using event_tracking_parse_t = SERVICE_TYPE_NO_CONST(event_tracking_parse); using event_tracking_query_t = SERVICE_TYPE_NO_CONST(event_tracking_query); using event_tracking_stored_program_t = SERVICE_TYPE_NO_CONST(event_tracking_stored_program); using event_tracking_table_access_t = SERVICE_TYPE_NO_CONST(event_tracking_table_access); SERVICE_TYPE(event_tracking_authentication) *srv_event_tracking_authentication = nullptr; SERVICE_TYPE(event_tracking_command) *srv_event_tracking_command = nullptr; SERVICE_TYPE(event_tracking_connection) *srv_event_tracking_connection = nullptr; SERVICE_TYPE(event_tracking_general) *srv_event_tracking_general = nullptr; SERVICE_TYPE(event_tracking_global_variable) *srv_event_tracking_global_variable = nullptr; SERVICE_TYPE(event_tracking_lifecycle) *srv_event_tracking_lifecycle = nullptr; SERVICE_TYPE(event_tracking_message) *srv_event_tracking_message = nullptr; SERVICE_TYPE(event_tracking_parse) *srv_event_tracking_parse = nullptr; SERVICE_TYPE(event_tracking_query) *srv_event_tracking_query = nullptr; SERVICE_TYPE(event_tracking_stored_program) *srv_event_tracking_stored_program = nullptr; SERVICE_TYPE(event_tracking_table_access) *srv_event_tracking_table_access = nullptr; static bool inited = false; void init_srv_event_tracking_handles() { srv_registry->acquire("event_tracking_authentication.mysql_server", reinterpret_cast( const_cast( &srv_event_tracking_authentication))); srv_registry->acquire( "event_tracking_command.mysql_server", reinterpret_cast(const_cast( &srv_event_tracking_command))); srv_registry->acquire("event_tracking_connection.mysql_server", reinterpret_cast( const_cast( &srv_event_tracking_connection))); srv_registry->acquire( "event_tracking_general.mysql_server", reinterpret_cast(const_cast( &srv_event_tracking_general))); srv_registry->acquire("event_tracking_global_variable.mysql_server", reinterpret_cast( const_cast( &srv_event_tracking_global_variable))); srv_registry->acquire("event_tracking_lifecycle.mysql_server", reinterpret_cast( const_cast( &srv_event_tracking_lifecycle))); srv_registry->acquire( "event_tracking_message.mysql_server", reinterpret_cast(const_cast( &srv_event_tracking_message))); srv_registry->acquire( "event_tracking_parse.mysql_server", reinterpret_cast( const_cast(&srv_event_tracking_parse))); srv_registry->acquire( "event_tracking_query.mysql_server", reinterpret_cast( const_cast(&srv_event_tracking_query))); srv_registry->acquire("event_tracking_stored_program.mysql_server", reinterpret_cast( const_cast( &srv_event_tracking_stored_program))); srv_registry->acquire("event_tracking_table_access.mysql_server", reinterpret_cast( const_cast( &srv_event_tracking_table_access))); assert(srv_event_tracking_authentication != nullptr && srv_event_tracking_command != nullptr && srv_event_tracking_connection != nullptr && srv_event_tracking_general != nullptr && srv_event_tracking_global_variable != nullptr && srv_event_tracking_lifecycle != nullptr && srv_event_tracking_message != nullptr && srv_event_tracking_parse != nullptr && srv_event_tracking_query != nullptr && srv_event_tracking_stored_program != nullptr && srv_event_tracking_table_access != nullptr); inited = true; } void deinit_srv_event_tracking_handles() { if (inited) { srv_registry->release(reinterpret_cast( const_cast( srv_event_tracking_authentication))); srv_registry->release(reinterpret_cast( const_cast(srv_event_tracking_command))); srv_registry->release(reinterpret_cast( const_cast( srv_event_tracking_connection))); srv_registry->release(reinterpret_cast( const_cast(srv_event_tracking_general))); srv_registry->release(reinterpret_cast( const_cast( srv_event_tracking_global_variable))); srv_registry->release( reinterpret_cast(const_cast( srv_event_tracking_lifecycle))); srv_registry->release(reinterpret_cast( const_cast(srv_event_tracking_message))); srv_registry->release(reinterpret_cast( const_cast(srv_event_tracking_parse))); srv_registry->release(reinterpret_cast( const_cast(srv_event_tracking_query))); srv_registry->release(reinterpret_cast( const_cast( srv_event_tracking_stored_program))); srv_registry->release(reinterpret_cast( const_cast( srv_event_tracking_table_access))); } inited = false; } namespace { /** Check, whether masks specified by lhs parameter and rhs parameters overlap. @param lhs First mask to check. @param rhs Second mask to check. @return false, when masks overlap, otherwise true. */ static inline bool check_audit_mask(const unsigned long lhs, const unsigned long rhs) { return !(lhs & rhs); } /** Dispatches an event by invoking the plugin's event_notify method. @param[in] thd Session THD containing references to the audit plugins. @param[in] plugin Plugin used for dispatching the event. @param[in] arg Opaque event data structure. @retval false always */ static int plugins_dispatch(THD *thd, plugin_ref plugin, void *arg) { const struct st_mysql_event_plugin_generic *event_generic = (const struct st_mysql_event_plugin_generic *)arg; unsigned long subclass = static_cast( *static_cast(event_generic->event)); st_mysql_audit *data = plugin_data(plugin); /* Check to see if the plugin is interested in this event */ if (check_audit_mask(data->class_mask[event_generic->event_class], subclass)) return 0; /* Actually notify the plugin */ return data->event_notify(thd, event_generic->event_class, event_generic->event); } static bool plugins_dispatch_bool(THD *thd, plugin_ref plugin, void *arg) { return plugins_dispatch(thd, plugin, arg) ? true : false; } /** Distributes an audit event to plug-ins @param[in] thd THD that generated the event. @param event_class Audit event class. @param[in] event Opaque pointer to the event data. */ int event_class_dispatch(THD *thd, mysql_event_class_t event_class, const void *event) { int result = 0; struct st_mysql_event_plugin_generic event_generic; event_generic.event_class = event_class; event_generic.event = event; /* Check if we are doing a slow global dispatch. This event occurs when thd == NULL as it is not associated with any particular thread. */ if (unlikely(!thd)) { return plugin_foreach(thd, plugins_dispatch_bool, MYSQL_AUDIT_PLUGIN, &event_generic) ? 1 : 0; } else { plugin_ref *plugins, *plugins_last; /* Use the cached set of audit plugins */ plugins = thd->audit_class_plugins.begin(); plugins_last = thd->audit_class_plugins.end(); for (; plugins != plugins_last; plugins++) result |= plugins_dispatch(thd, *plugins, &event_generic); } return result; } static const CHARSET_INFO *get_charset_from_thd(THD *thd) { if (thd->rewritten_query().length() > 0) return thd->rewritten_query().charset(); return thd->charset(); } /** Fill query info extracted from the thread object and return the thread object charset info. @param[in] thd Thread data. @param[out] query SQL query text. @return SQL query charset. */ inline const CHARSET_INFO *thd_get_audit_query(THD *thd, LEX_CSTRING *query) { /* If we haven't tried to rewrite the query to obfuscate passwords etc. yet, do so now. */ if (thd->rewritten_query().length() == 0) mysql_rewrite_query(thd); /* If there was something to rewrite, use the rewritten query; otherwise, just use the original as submitted by the client. */ if (thd->rewritten_query().length() > 0) { query->str = thd->rewritten_query().ptr(); query->length = thd->rewritten_query().length(); return thd->rewritten_query().charset(); } else { query->str = thd->query().str; query->length = thd->query().length; return thd->charset(); } } } // namespace DEFINE_BOOL_METHOD(Event_authentication_bridge_implementation::notify, (const mysql_event_tracking_authentication_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; auto event_information = static_cast( thd->get_event_tracking_data().second); mysql_event_authentication plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_AUTHENTICATION_FLUSH: plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_FLUSH; break; case EVENT_TRACKING_AUTHENTICATION_AUTHID_CREATE: plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_AUTHID_CREATE; break; case EVENT_TRACKING_AUTHENTICATION_CREDENTIAL_CHANGE: plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_CREDENTIAL_CHANGE; break; case EVENT_TRACKING_AUTHENTICATION_AUTHID_RENAME: plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_AUTHID_RENAME; break; case EVENT_TRACKING_AUTHENTICATION_AUTHID_DROP: plugin_data.event_subclass = MYSQL_AUDIT_AUTHENTICATION_AUTHID_DROP; break; default: assert(false); break; } const char *auth_method = event_information->authentication_methods_.size() ? event_information->authentication_methods_[0] : nullptr; plugin_data.authentication_plugin = {auth_method, auth_method ? strlen(auth_method) : 0}; plugin_data.connection_id = data->connection_id; plugin_data.host = TO_LEXCSTRING(data->host); plugin_data.is_role = event_information->is_role_; plugin_data.new_host = TO_LEXCSTRING(event_information->new_host_); plugin_data.new_user = TO_LEXCSTRING(event_information->new_user_); plugin_data.query_charset = thd_get_audit_query(thd, &plugin_data.query); plugin_data.sql_command_id = thd->lex->sql_command; plugin_data.status = data->status; plugin_data.user = TO_LEXCSTRING(data->user); return event_class_dispatch(thd, MYSQL_AUDIT_AUTHENTICATION_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_command_bridge_implementation::notify, (const mysql_event_tracking_command_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_command plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_COMMAND_START: plugin_data.event_subclass = MYSQL_AUDIT_COMMAND_START; break; case EVENT_TRACKING_COMMAND_END: plugin_data.event_subclass = MYSQL_AUDIT_COMMAND_END; break; default: assert(false); break; } plugin_data.command_id = get_server_command(data->command.str); plugin_data.connection_id = data->connection_id; plugin_data.status = data->status; return event_class_dispatch(thd, MYSQL_AUDIT_COMMAND_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_connection_bridge_implementation::notify, (const mysql_event_tracking_connection_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_connection plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_CONNECTION_CONNECT: plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_CONNECT; break; case EVENT_TRACKING_CONNECTION_DISCONNECT: plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_DISCONNECT; break; case EVENT_TRACKING_CONNECTION_CHANGE_USER: plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_CHANGE_USER; break; case EVENT_TRACKING_CONNECTION_PRE_AUTHENTICATE: plugin_data.event_subclass = MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE; break; default: assert(false); break; } plugin_data.status = data->status; plugin_data.connection_id = data->connection_id; plugin_data.user = TO_LEXCSTRING(data->user); plugin_data.priv_user = TO_LEXCSTRING(data->priv_user); plugin_data.external_user = TO_LEXCSTRING(data->external_user); plugin_data.proxy_user = TO_LEXCSTRING(data->proxy_user); plugin_data.host = TO_LEXCSTRING(data->host); plugin_data.ip = TO_LEXCSTRING(data->ip); plugin_data.database = TO_LEXCSTRING(data->database); plugin_data.connection_type = data->connection_type; return event_class_dispatch(thd, MYSQL_AUDIT_CONNECTION_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_general_bridge_implementation::notify, (const mysql_event_tracking_general_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; auto event_information = static_cast( thd->get_event_tracking_data().second); mysql_event_general plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_GENERAL_LOG: plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_LOG; break; case EVENT_TRACKING_GENERAL_ERROR: plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_ERROR; break; case EVENT_TRACKING_GENERAL_STATUS: plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_STATUS; break; case EVENT_TRACKING_GENERAL_RESULT: plugin_data.event_subclass = MYSQL_AUDIT_GENERAL_RESULT; break; default: assert(false); break; }; plugin_data.general_error_code = data->error_code; plugin_data.general_thread_id = data->connection_id; plugin_data.general_user = TO_LEXCSTRING(data->user); plugin_data.general_ip = TO_LEXCSTRING(data->ip); plugin_data.general_host = TO_LEXCSTRING(data->host); plugin_data.general_external_user = TO_LEXCSTRING(event_information->external_user_); plugin_data.general_rows = static_cast(event_information->rows_); if (event_information->command_.str != nullptr && thd->lex->sql_command == SQLCOM_END && thd->get_command() != COM_QUERY) { plugin_data.general_sql_command = {STRING_WITH_LEN("")}; } else { plugin_data.general_sql_command = sql_statement_names[thd->lex->sql_command]; } plugin_data.general_charset = const_cast( thd_get_audit_query(thd, &plugin_data.general_query)); plugin_data.general_time = static_cast(event_information->time_); plugin_data.general_command = {event_information->command_.str, event_information->command_.length}; return event_class_dispatch(thd, MYSQL_AUDIT_GENERAL_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_global_variable_bridge_implementation::notify, (const mysql_event_tracking_global_variable_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_global_variable plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_GLOBAL_VARIABLE_GET: plugin_data.event_subclass = MYSQL_AUDIT_GLOBAL_VARIABLE_GET; break; case EVENT_TRACKING_GLOBAL_VARIABLE_SET: plugin_data.event_subclass = MYSQL_AUDIT_GLOBAL_VARIABLE_SET; break; default: assert(false); break; } plugin_data.connection_id = data->connection_id; plugin_data.sql_command_id = thd->lex->sql_command; plugin_data.variable_name = TO_LEXCSTRING(data->variable_name); plugin_data.variable_value = TO_LEXCSTRING(data->variable_value); return event_class_dispatch(thd, MYSQL_AUDIT_GLOBAL_VARIABLE_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_lifecycle_bridge_implementation::notify_shutdown, (const mysql_event_tracking_shutdown_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_server_shutdown plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_SHUTDOWN_SHUTDOWN: plugin_data.event_subclass = MYSQL_AUDIT_SERVER_SHUTDOWN_SHUTDOWN; break; default: assert(false); break; } plugin_data.exit_code = data->exit_code; switch (data->reason) { case EVENT_TRACKING_SHUTDOWN_REASON_SHUTDOWN: plugin_data.exit_code = MYSQL_AUDIT_SERVER_SHUTDOWN_REASON_SHUTDOWN; break; case EVENT_TRACKING_SHUTDOWN_REASON_ABORT: plugin_data.reason = MYSQL_AUDIT_SERVER_SHUTDOWN_REASON_ABORT; break; default: assert(false); break; } return event_class_dispatch(thd, MYSQL_AUDIT_SERVER_SHUTDOWN_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_lifecycle_bridge_implementation::notify_startup, (const mysql_event_tracking_startup_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_server_startup plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_STARTUP_STARTUP: plugin_data.event_subclass = MYSQL_AUDIT_SERVER_STARTUP_STARTUP; break; default: assert(false); break; } plugin_data.argc = data->argc; plugin_data.argv = data->argv; return event_class_dispatch(thd, MYSQL_AUDIT_SERVER_STARTUP_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_message_bridge_implementation::notify, (const mysql_event_tracking_message_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_message plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_MESSAGE_INTERNAL: plugin_data.event_subclass = MYSQL_AUDIT_MESSAGE_INTERNAL; break; case EVENT_TRACKING_MESSAGE_USER: plugin_data.event_subclass = MYSQL_AUDIT_MESSAGE_USER; break; default: assert(false); break; } std::unique_ptr local_key_value_map( data->key_value_map_length > 0 ? new mysql_event_message_key_value_t[data->key_value_map_length] : nullptr); mysql_event_message_key_value_t *local_kv = local_key_value_map.get(); mysql_event_tracking_message_key_value_t *kv = data->key_value_map; for (size_t i = 0; i < data->key_value_map_length; ++i, ++local_kv, ++kv) { local_kv->key = {kv->key.str, kv->key.length}; switch (kv->value_type) { case EVENT_TRACKING_MESSAGE_VALUE_TYPE_STR: local_kv->value_type = MYSQL_AUDIT_MESSAGE_VALUE_TYPE_STR; local_kv->value.str = {kv->value.str.str, kv->value.str.length}; break; case EVENT_TRACKING_MESSAGE_VALUE_TYPE_NUM: local_kv->value_type = MYSQL_AUDIT_MESSAGE_VALUE_TYPE_NUM; local_kv->value.num = kv->value.num; break; default: assert(false); break; } } plugin_data.component = TO_LEXCSTRING(data->component); plugin_data.key_value_map = local_key_value_map.get(); plugin_data.key_value_map_length = data->key_value_map_length; plugin_data.message = TO_LEXCSTRING(data->message); plugin_data.producer = TO_LEXCSTRING(data->producer); return event_class_dispatch(thd, MYSQL_AUDIT_MESSAGE_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_parse_bridge_implementation::notify, (mysql_event_tracking_parse_data * data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_parse plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_PARSE_PREPARSE: plugin_data.event_subclass = MYSQL_AUDIT_PARSE_PREPARSE; break; case EVENT_TRACKING_PARSE_POSTPARSE: plugin_data.event_subclass = MYSQL_AUDIT_PARSE_POSTPARSE; break; default: assert(false); break; } mysql_event_parse_rewrite_plugin_flag plugin_flag; switch (*(data->flags)) { case EVENT_TRACKING_PARSE_REWRITE_NONE: plugin_flag = MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_NONE; break; case EVENT_TRACKING_PARSE_REWRITE_QUERY_REWRITTEN: plugin_flag = MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_QUERY_REWRITTEN; break; case EVENT_TRACKING_PARSE_REWRITE_IS_PREPARED_STATEMENT: plugin_flag = MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_IS_PREPARED_STATEMENT; break; default: assert(false); break; } LEX_CSTRING rewritten_query{nullptr, 0}; plugin_data.flags = &plugin_flag; plugin_data.query = TO_LEXCSTRING(data->query); plugin_data.rewritten_query = nullptr; if (data->rewritten_query) { plugin_data.rewritten_query = &rewritten_query; } bool retval = event_class_dispatch(thd, MYSQL_AUDIT_PARSE_CLASS, &plugin_data); if ((int)plugin_flag & EVENT_TRACKING_PARSE_REWRITE_QUERY_REWRITTEN) { *(data->flags) |= EVENT_TRACKING_PARSE_REWRITE_QUERY_REWRITTEN; if (data->rewritten_query) { data->rewritten_query->str = rewritten_query.str; data->rewritten_query->length = rewritten_query.length; } } return retval; } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_query_bridge_implementation::notify, (const mysql_event_tracking_query_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_query plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_QUERY_START: plugin_data.event_subclass = MYSQL_AUDIT_QUERY_START; break; case EVENT_TRACKING_QUERY_NESTED_START: plugin_data.event_subclass = MYSQL_AUDIT_QUERY_NESTED_START; break; case EVENT_TRACKING_QUERY_STATUS_END: plugin_data.event_subclass = MYSQL_AUDIT_QUERY_STATUS_END; break; case EVENT_TRACKING_QUERY_NESTED_STATUS_END: plugin_data.event_subclass = MYSQL_AUDIT_QUERY_NESTED_STATUS_END; break; default: assert(false); break; } plugin_data.connection_id = data->connection_id; plugin_data.query = TO_LEXCSTRING(data->query); plugin_data.query_charset = get_charset_from_thd(thd); plugin_data.sql_command_id = thd->lex->sql_command; plugin_data.status = data->status; return event_class_dispatch(thd, MYSQL_AUDIT_QUERY_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_stored_program_bridge_implementation::notify, (const mysql_event_tracking_stored_program_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_stored_program plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_STORED_PROGRAM_EXECUTE: plugin_data.event_subclass = MYSQL_AUDIT_STORED_PROGRAM_EXECUTE; break; default: assert(false); break; } plugin_data.connection_id = data->connection_id; plugin_data.database = TO_LEXCSTRING(data->database); plugin_data.name = TO_LEXCSTRING(data->name); plugin_data.parameters = data->parameters; plugin_data.query_charset = thd_get_audit_query(thd, &plugin_data.query); plugin_data.sql_command_id = thd->lex->sql_command; return event_class_dispatch(thd, MYSQL_AUDIT_STORED_PROGRAM_CLASS, &plugin_data); } catch (...) { return true; } } DEFINE_BOOL_METHOD(Event_table_access_bridge_implementation::notify, (const mysql_event_tracking_table_access_data *data)) { try { if (data == nullptr) return false; THD *thd = current_thd; mysql_event_table_access plugin_data; switch (data->event_subclass) { case EVENT_TRACKING_TABLE_ACCESS_READ: plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_READ; break; case EVENT_TRACKING_TABLE_ACCESS_INSERT: plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_INSERT; break; case EVENT_TRACKING_TABLE_ACCESS_UPDATE: plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_UPDATE; break; case EVENT_TRACKING_TABLE_ACCESS_DELETE: plugin_data.event_subclass = MYSQL_AUDIT_TABLE_ACCESS_DELETE; break; default: assert(false); break; } plugin_data.connection_id = data->connection_id; plugin_data.sql_command_id = thd->lex->sql_command; plugin_data.query_charset = thd_get_audit_query(thd, &plugin_data.query); plugin_data.table_database = TO_LEXCSTRING(data->table_database); plugin_data.table_name = TO_LEXCSTRING(data->table_name); return event_class_dispatch(thd, MYSQL_AUDIT_TABLE_ACCESS_CLASS, &plugin_data); } catch (...) { return true; } }